#Password security fix
There are several ways to implement the latest security update for PrestaShop. Choose the one you feel confident with!
- For Git user: Cherry-pick
- For Unix user: Apply the patch with diff files
- For others: Copy the patched files
- For those who like a challenge: The ‘Do it Yourself’ method!
For other situations (old versions of PrestaShop, heavily customized Core files), you should do it yourself! If you have a little bit of PHP experience, it is easy to do.
##Cherry-picking the commits
If you use Git, you can just cherry-pick the commits for the right version.
###1.6.0.14
For this version, there are two commits to use:
###1.5.6.2
For version 1.5.6.2, there is only one commit to cherry-pick: f1ef8aa913ea013c42d9f2d702142caf031b4945
###1.4.11.0
For version 1.4.11.0, use this one commit for the cherry-pick: 44b811cdd1266a31e8f6754f6fd5c1b10bdef690
##Using the diff files
If you are a Linux/Unix user, you can use patch to modify the files. Only use this if you know what you are doing!
##Using the patched files
Here are the zip files for each version:
The admin folder name can differ from yours: make sure you rename it with the right folder name! Be careful, if you have made some modifications on some of theses files, you will lose them. In this case, instead of copying the whole files, you should modify each files as explained in the ‘Do it yourself!’ section.
##Do it yourself!
The modification to apply is easy to understand:
Look at the PrestaShop 1.5.6.2 commit here.
Follow these steps:
###Modify the Tools password generation
I. Modify the Tools passwdGen method
The method passwdGen() will take two arguments : $length and $flag. Older versions only take one argument ($length). The code should do a switch() from this flag: if there is no switch on older versions, just add it.
Your method will now have something like this:
switch ($flag) {
case 'NUMERIC':
$str = '0123456789';
break;
case 'NO_NUMERIC':
$str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
case 'RANDOM':
$num_bytes = ceil($length * 0.75);
$bytes = Tools::getBytes($num_bytes);
return substr(rtrim(base64_encode($bytes), '='), 0, $length);
case 'ALPHANUMERIC':
default:
$str = 'abcdefghijkmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
}
Then the password generation just below should look like this:
$bytes = Tools::getBytes($length);
$position = 0;
$result = '';
for ($i = 0; $i < $length; $i++) {
$position = ($position + ord($bytes[$i])) % strlen($str);
$result .= $str[$position];
}
Perfect, there is now a new method called getBytes($length). Just add it! Copy/paste this:
public static function getBytes($length)
{
$length = (int)$length;
if ($length <= 0) {
return false;
}
if (function_exists('openssl_random_pseudo_bytes')) {
$bytes = openssl_random_pseudo_bytes($length, $crypto_strong);
if ($crypto_strong === true) {
return $bytes;
}
}
if (function_exists('mcrypt_create_iv')) {
$bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
if ($bytes !== false && strlen($bytes) === $length) {
return $bytes;
}
}
// Else try to get $length bytes of entropy
$result = '';
$entropy = '';
$msec_per_round = 400;
$bits_per_round = 2;
$total = $length;
$hash_length = 20;
while (strlen($result) < $length) {
$bytes = ($total > $hash_length) ? $hash_length : $total;
$total -= $bytes;
for ($i=1; $i < 3; $i++) {
$t1 = microtime(true);
$seed = mt_rand();
for ($j=1; $j < 50; $j++) {
$seed = sha1($seed);
}
$t2 = microtime(true);
$entropy .= $t1 . $t2;
}
$div = (int) (($t2 - $t1) * 1000000);
if ($div <= 0) {
$div = 400;
}
$rounds = (int) ($msec_per_round * 50 / $div);
$iter = $bytes * (int) (ceil(8 / $bits_per_round));
for ($i = 0; $i < $iter; $i ++) {
$t1 = microtime();
$seed = sha1(mt_rand());
for ($j = 0; $j < $rounds; $j++) {
$seed = sha1($seed);
}
$t2 = microtime();
$entropy .= $t1 . $t2;
}
$result .= sha1($entropy, true);
}
return substr($result, 0, $length);
}
Wonderful, the hardest part is done!
II. [For 1.4 versions only] Change the password validity
This version is using a regex to check the password validity. Replace it with the following method on the Validate class:
public static function isPasswd($passwd, $size = 5)
{
return (strlen($passwd) >= $size && strlen($passwd) < 255);
}
III. Modify the customer password generation call
Now find your AdminLoginController, and the processForgot() method, which is called when the user wants to get a new password.
It should contain a call to our Tools method like this:
$password = Tools::passwdGen(MIN_PASSWD_LENGTH)
Just replace it with this one:
$password = Tools::passwdGen(MIN_PASSWD_LENGTH, 'RANDOM')
IV. Modify the admin password generation call
The final step is the call from the administrators (same as for the customer).
There was an admin controller named AdminLoginController or, on older versions, a file named password.php.
Find the process called (on the controller) processForgot() and the call to our Tools method:
$pwd = Tools::passwdGen();
And just replace it with:
$pwd = Tools::passwdGen(10, 'RANDOM');
It is done, you did it!
Hi @shkurkin,
Thank you for your feedback, there is some errors on my gist :( I've made a change on the wrong section ><
I'll fix this as fast as possible.
Sorry !