AppSec Blog

Spot the Vuln - Action - Defense in Depth


Affected Software: PixelPost

Fixed in Version: ?

Issue Type: Insecure password reset functionality

Original Code: Found Here


This week's bug is more of a design issue as opposed to an implementation issue. I actually first heard about this code from SkullSecurity's excellent articles on "Hacking Crappy Password Resets" articles published in late March. SkullSecurity does an excellent job of explaining that line 31(the line that does the actual password generation) is full of bad security design. First, the password reset code is using the MD5() function in PHP. MD5() takes a string and returns a MD5 hash of that string. In this password reset code, we see that we are hashing the value of: 'time' . rand(1, 16000). SkullSecurity astutely points out that the password reset code is using the string ?time', NOT the function time() which would return the number of seconds since epoch. The string ?time' has a random number between 1 and 16000 appended to it and is passed to MD5(). Assuming rand() generates a truly random number between 1 and 16000, we only have 16000 possible combinations for the newly reset password. Unfortunately, in many circumstances, rand() doesn't generate a truly random number. Many times, rand() will use system time as a seed value and produce a number that is predictable. In many cases, it may be possible to predict or leak system time for the machine generating the random password. Stefan Esser wrote an excellent article on the challenges of generating random numbers in PHP which covers many of these scenarios.

So what should the author do? Change ?time' to time()? Even if the author changed the string ?time' to the function time(), time() in PHP only returns seconds since epoch time. If an attacker can leak system time, they'll still be able to predict the password reset value?

Developers Solution

<?php ...snip... // forgot password? if(isset($_GET['x']) && $_GET['x']=='passreminder') { echo ' $admin_lang_pw_title

'; if ($cfgrow['admin']!= $_POST['user']) { echo "$admin_lang_pw_wronguser "; echo " $admin_lang_pw_back"; die(); } if ($cfgrow['email']== "") { echo "$admin_lang_pw_noemail "; echo " $admin_lang_pw_back "; die(); } if (strtolower($cfgrow['email'])==strtolower($_POST['reminderemail'])) { // generate a random new pass $user_pass = substr( MD5('time' . rand(1, 16000)), 0, 6); $query = "update ".$pixelpost_db_prefix."config set password=MD5('$user_pass') where admin='".$cfgrow['admin']."'"; if(mysql_query($query)) { $subject = "$admin_lang_pw_subject"; $body = "$admin_lang_pw_text_1 \n\n"; $body .= "$admin_lang_pw_usertext ".$cfgrow['admin']." \n"; $body .= "$admin_lang_pw_mailtext ".$cfgrow['email']." \n\n"; $body .= "$admin_lang_pw_newpw $user_pass"; $body .= "\n\n$admin_lang_pw_text_7".$cfgrow['siteurl']."admin $admin_lang_pw_text_8"; $headers = "Content-type: text/plain; charset=UTF-8\n"; $headers .= "$admin_lang_pw_text_2 \n"; $recipient_email = $cfgrow['email']; if (mail($recipient_email,$subject,$body,$headers)) {echo "$admin_lang_pw_text_3" .$cfgrow['email'];} else { echo "$admin_lang_pw_text_3";} echo " $admin_lang_pw_back "; die(); } else { $dberror = mysql_error(); echo "$admin_lang_pw_text_5 " .$dberror ."$admin_lang_pw_text_5 " ; echo " $admin_lang_pw_back "; die(); } } else { echo "$admin_lang_pw_notsent "; echo " $admin_lang_pw_back "; die(); }// end else (strtolower($cfgrow['email'])==strtolower($_POST['reminderemail']) & $cfgrow['email']!= "") } // end if($_GET['x']=='passreminder') ?>

Post a Comment


* Indicates a required field.