Vulnerabilities in Password Reset Process
“Forgot password” is a must have feature in nearly every website that has its own account system. However, it’s usually one of the most vulnerable parts in a website. As common and pervasive as it is, it can be a tantalizing target of malicious network attacks.
There were a few good articles summarizing the bugs and fixes in a password reset process, posted on wooyun.org. However, the website was taken down in 2016, and all posts now are not accessible. Fortunately, we have archive.org 👍 and most of these articles have been archived.
All the examples were bugs found in real world applications or websites. These bugs were found years ago and all had been fixed before this post, so don’t bother 😀
Types of vulnerabilities
Vulnerable to Brute-Force Attacks
Is 2-step verification a guarantee of absolute security? NO. Actually, a website can be more vulnerable with a poorly implemented 2-step verification than without it. The SMS verification code is a common approach of 2-step verification, also a common way of authentication before resetting passwords. Usually, a verification code includes only 4 to 6 digits.
Example: dangdang.com (Sep. 2012)
A pair of a mobile number and a code can be submitted for unlimited times. The code only has 4 digits. Easy to find the correct combination in a few minutes by brute-force approaches.
Example: WeChat (Sep. 2012)
WeChat had a limited number of attempts, but didn’t filter the mobile number before checking attempt times. For instance, if an attacker submits a mobile number=“123456789ab”, WeChat would first check whether the mobile number “123456789ab” has reached the attempt limit, and then extract the true mobile number (123456789) from the string and verify the code. So, the attacker can keep changing the suffix to bypass the limit.
Token in URLs
Never put the verification token in a url. Even if the url won’t be shown in the browser, an attacker can easily capture it with a packet analyzer.
Example: xiu.com (Mar. 2012)
After a user requesting password reset by submitting a mobile number, the following url will be requested in the background:
“{website_domain}/ajax/sms.php?action=user_reset&mobile={mobile_number}&verifycode={code}&time=0.39637206563755256”
where code is a 4-digit number.
Example: tiantian.com (May, 2014)
After submitting a request, one can capture a packet including an encrypted string:
“{value:[“Success”, “2”, …, “{string}”], error: null}“
and one can access the password reset page via:
“login.{website_domain}/new/modify_password/{string}
where the two strings are identical.
Credential in Webpages
Never put user’s credential information in the webpages, even in a hidden element. It seems to be a stupid idea, but it did happen in enterprise-class applications.
Example: sohu.com email (Feb, 2012)
sohu.com required users to set up three security questions while registering, and a user could reset his password by answering all the question. However, the correct answer to those questions were accidentally placed in the webpages (in the source code but not displayed).
Guessable Tokens
Another common approach is sending an email to the user who forgot his password, with a secret url inside to reset his password. Ensure the url is not guessable by other users. For example, do not use a timestamp or auto-incremental id, even after hashing.
Example: 360.cn (June, 2012)
A user will receive an email including the following url after submitting a password reset request:
“{website_domain}/findpwd/setpwdfromemail?vc={timestamp}&u={username}%40gmail.com)“
The timestamp is hashed with md5, but is still guessable because it’s a timestamp just before the email was sent.
Example: ZTE appstar.com.cn (Jan, 2015)
A user will receive a url via email. However, the url includes a token which is auto incremented.
Token Validation
SMS tokens or email tokens should be validated with the corresponding user. Always check whether the user who submit a token is the one who requested this token.
Example: OPPO nearme.com.cn (Mar, 2014)
Submit a password reset request with own mobile number and receive a SMS including a 4-digit code. Then submit another password reset request with number “18688888888”, and use the code we received before.
Example: idtag.cn (Sep, 2012)
One can receive an email with a following url after submitting a password reset request:
“{website_domain}/regionTempAction.do?method=resetPassword&idtagCard={userid}&authcode=Go8K7yp4TWy&rtEmail={email}“
After setting a new password, one can capture a token:
org.apache.struts.taglib.html.TOKEN=83accc27d5178f832d9f22a1d02bdacf&org.apache.struts.taglib.html.TOKEN=83accc27d5178f832d9f22
Using this token can modified any user’s password.
Binding Mobile Phone
Double check before binding a mobile number to a user.
Example: 126.com (June, 2012)
Register a new account without adding a mobile number, then navigate to the page that binding security phones. The page is identified by a token in the url, which could be replaced by a id of another existing user. If one knows the id of some user, then can binding his own mobile number to that user and reset the password.
Email Validation
If you use email to send the password reset url, you probably also need a link to let the user resend the email in case of missing the first one. Make sure the email will be sent to the email address bound to the user, not the one user just inputs.
Example: hexun.com (Jan, 2015)
Click the resend button and capture the request url. Replace the email address in the url with an email of your own. Then the url will be sent to your inbox.
Skippable Steps
If you use a multi step form for password reset process, make sure every step is not skippable. A user can still get access to the next step by manually entering the url, even if you disable the link to the next step on the webpage.
Example: OPPO nearme.com.cn (Nov, 2013)
There were four validation steps before you could change the password. However, you can directly get to the final one via this url:
“uc.{website_domain}/usercenter/resetPassword.do”
Client-Side Validation
The logic that checks the validation information should always exist on the server side.
Example: lefeng.com (Nov, 2014)
Request a password reset, then enter a validation code randomly. Intercept the packet that was sent from the server, and change the status code to 200. Then we are navigated to the password reset page.
Session
Ensuring a valid session or cookie is not enough. Always check the current session matches with the modified user.
Example: jumei.com (Dec, 2014)
Firstly, request password reset with your own email and get a url. With the same browser, submit another password reset request to another email. Now access the url that we got in the first step, and modified the password of that email.