As discussed in a current bugreport the current stable PHP version has a serious issue. This issue affects security critically as it might be the opener for some serious authentication problems. Let’s have a look on the facts.

photo credit: torbakhopper
PHP v5.3.7 is offering the crypt() function which is a proxy for several encryption algorithms. It supports DES, MD5, Blowfish and several SHA variants. It can be salted optionally and comes with its own implementation of these algorithms since PHP 5.3.0 (it relied on available system functions before). We have discussed earlier why salting is good when using hash functions. When passing an optional salt parameter crypt() decides upon the structure of the salt which hashing algorithm to use else it defaults to the standard DES.
So passing a salt is a desired behavior. When the coder passes a salt then crypt() mangles the salt and the string which makes the hash more robust/invulnerable to rainbow table attacks etc. Here’s an example PHP code taken from the manual page of crypt():
<?php
if (CRYPT_MD5 == 1) {
// function call: crypt($stringToHash, $salt)
echo 'MD5: ' . crypt('rasmuslerdorf', '$1$rasmusle$') . "\n";
}
?>
In working versions of PHP this code will result in the following output:
MD5: $1$rasmusle$rISCgZzpwk3UhDidwXvin0
Great! We have a crypt() output containing the salt and a MD5 digest that has been generated based on the input and the salt. This is fine. Now for the problem.
Crippled crypt() in PHP 5.3.7
The problem is that the current stable PHP version, which is 5.3.7, will output only the salt and forget about the entered string when using MD5 salts. So the same code above results in the following output:
MD5: $1$rasmusle
Imagine the common scenario for persistance:
<?php
/**
* @param string $salt in this example a defined "constant" containing the salt to use while hashing the $userPassword
*/
$salt = '$1$rasmusle$';
/**
* Persisting the user credentials
* @param string $somePseudoID a fake identifier here for example purposes only. Don't ask where it came from.
* @param string $userPasswordPlainText has been entered by user
*/
$tempHashedPassword = crypt($userPasswordPlainText,$salt);
somePseudoStoreFunction($somePseudoID,$tempHashedPassword);
?>
Because of the broken crypt function the user data store contains only the salt, not the hashed digest! Imagine a database table full of salts where actually salted passwords should be. Let’s authenticate against it:
<?php
/**
* @param string $salt in this example a defined "constant" containing the salt to use while hashing the $userPassword
*/
$salt = '$1$rasmusle$';
/**
* @param string $userPassword has been entered by user
* @param string $storedUserPassword persisted string containing the initially given hashsum to compare with
*/
$storedUserPassword = somePseudoRetrieveFunction($somePseudoID);
if ($storedUserPassword == crypt($userPassword, $salt)) {
echo 'Authentication succeeded.';
}
?>
Now the crypt function outputs the salt only again – regardless of the entered string. Because it is been compared to the persisted hashsum consisting of the salt only the authentication scheme works always. It just does not matter which string has been inserted. This means an attacker could enter any password and access will be granted!
Solution
The bug has been solved in Subversion already. This means webserver administrators will have to update their machines and installations with a current PHP development snapshot. This implies fetching an unstable version. For those unwilling to stick with unstable software: according to PHP staff admins should stop updating to 5.3.7 and wait for 5.3.8 instead which is due to be released in a few days because of this bug.

