Skip to content

/scripts/cpcpass

Here is /scripts/chcpass – the official command line password changing script for cPanel servers. (At least, their Technical Study Guide says it should be used to change passwords.) What does this script do? It does this:

root@host [~]# grep user2 /etc/shadow
user2:$1$3kKhYXle$Zyy0ayNoXUX/ZBpDBtyey1:15261:0:99999:7:::

root@host [~]# /scripts/chcpass user2 newpassword
Changing password for user2
Password for user2 has been changed

root@host [~]# grep user2 /etc/shadow
user2:newpassword:15262:0:99999:7:::

As you can see, what it does is it corrupts the shadow file! Let’s see if we can find out what is wrong with chcpass, shall we?

#!/usr/bin/perl

use Fcntl ();

if ( -e "/etc/master.passwd" ) {
    die "rawchpass: fixme: freebsd please report this on support.cpanel.net";
}

my $user = $ARGV[0];
my $pass = $ARGV[1];

my @UP;
if ( $ARGV[0] eq "" ) {
    my $up = <STDIN>;
    chomp $up;
    @UP   = split( / /, $up );
    $user = $UP[0];
    $pass = $UP[1];
}

if ( $user eq "" ) { print "user is blank\n"; exit; }
if ( $pass eq "" ) { print "pass is blank\n"; exit; }

open( RANDOM, "/dev/urandom" );
my $random;
read RANDOM, $random, 4096;
close(RANDOM);

$random =~ s/\W//g;

my $cpass = $pass;
my $mytime = int( time / ( 60 * 60 * 24 ) );

open( SHADOW, "/etc/shadow" );
flock( SHADOW, Fcntl::LOCK_EX );
my @SHADOW = <SHADOW>;
flock( SHADOW, Fcntl::LOCK_UN );
close(SHADOW);

open( SHADOW, ">/etc/shadow" );
flock( SHADOW, Fcntl::LOCK_EX );
foreach my $line (@SHADOW) {
    if ( $line =~ /^$user:/ ) {
        $line =~ s/\n//g;

        print "Changing password for $user\n";

        #operator:*:10325:-1:-1:-1:-1:-1:-1
        my ( $g1, $g2, $g3, $g4, $g5, $g6 );
        ( undef, undef, undef, $g1, $g2, $g3, $g4, $g5, $g6 ) = split( /:/, $line );
        $line = join( ‘:’, $user, $cpass, $mytime, $g1, $g2, $g3, $g4, $g5, $g6 );
        $line = $line . "\n";
    }
    print SHADOW $line;
}
flock( SHADOW, Fcntl::LOCK_UN );
close(SHADOW);

print "Password for $user has been changed\n";

First, notice this: it does check it’s arguments, but does not bother printing any usage instructions if there is a problem. Also, there is no help option available, so the only way to know how to use the script is to read the code. This is not exactly user friendly.

After checking that the arguments are not blank, it reads 4k of data from /dev/urandom, removes non-word characters from it, and … well it does nothing with this data. It’s ignored after being read. Useless code is useless.

Next the script reads the entire contents of the /etc/shadow file into memory – not generally a good practice for code efficiency reasons, but probably OK, since most shadow files are not going to be that huge. At least the file is locked while accessing it. (Although, it should be noted that Perl’s standard file I/O is slow. Calling sysread would have been better.) The shadow file is closed after the read …

… and the very next line opens it back up. Couldn’t the script have just opened the file for read and write with mode +< and then called seek(SHADOW, SEEK_SET) to rewind the file to the beginning? Yes. And it would have been faster, too. There’s even a (very tiny) chance that between the file close and the re-open that some other process could open and lock the shadow file as part of adding or a deleting a user which would mean that when cpcpass writes the shadow file back to disk later on it would undo the changes made by the other process! This is why operations on /etc/passwd and /etc/shadow need to be atomic.

At last, though, the script goes through the data, finds the user, updates the password to be the raw string passed in (presumably this is intended to already be in the correctly encrypted form), writes the shadow data back, and prints a message that the password has been updated.

Well, sort of. Notice that the last line

print "Password for $user has been changed\n";

is always executed. Even if the user named on the command line is not found. This script always reports success, even when it succeeds.

Terrible. Code.

There is also /scripts/realrawchpass which is nearly identical, and /scripts/realchpass which just calls realrawchpass. There is a /scripts/chpass which calls /scripts/realchpass which finally looks like it might be code written by someone who had at least a clue or two. It verifies that the user exists first, it opens the shadow file for read write and maintains the lock during the entire operation, it checks to see what password encryption mechanism is in use on the system and actually uses it. It does have it’s own problems (such as reading /etc/pam.d/system-auth up to three times even though the returned value is always the same) but at least this version of the password changer seems like it might actually work!

Leave a Reply

*