Password Reset is one of the most common features in web applications. Every website that requires you to make an account also implements a way to reset your password. And while it is one of the most commonly implemented functions, it is also one of the least tested for security vulnerabilities. There are many ways an attacker could potentially get access to any user's account if the password reset implementation is weak.
Note: Some of the attacks in this post require you to intercept traffic and manipulate requests/responses. So, having some knowledge of Burp Suite/OWASP ZAP will certainly help as you read through the article.
Case 1: Host Header Injection
- Go to Reset Password page
- Turn on Burp Proxy
- Request password reset for your email and intercept the request
- Manipulate the Host header in the request to a website you control, say attacker.com
- Forward the request
- If the password reset link that you receive in your email contains attacker.com as the host instead of the actual website, the implementation is vulnerable
If the Host header does not work, you can even try your luck with the
X-Forwarded-Host: attacker.com
header instead.
The reason why this is a security issue is because if the user clicks on the attacker.com link in the email, it will send a request on the attacker.com server where the attacker can easily harvest the reset token from the URL from his server's logs.
This usually happens in PHP applications when the password reset link is built using https://$_SERVER['HTTP_HOST']/resetpassword?token=wcsajcdncsdklcsdnkvcsdk
A simple mitigation would be to use $_SERVER['SERVER_NAME'] instead of $_SERVER['HTTP_HOST'] to build the password reset link.
Case 2: HTTP Parameter Pollution
Using HTTP Parameter Pollution, an attacker can introduce multiple parameters with the same name into the request. In case of Password Reset, we can add another email parameter within our password reset request.
- Intercept Password Reset Request in Burp.
- Add another parameter email in the request
- Add attacker's email in the new parameter
If the web app is vulnerable, the email with password reset link will be sent to both the emails in the request.
You can also try some other variations of this:
URL encoding a space character: email=victim@email.com%20email=attacker@email.com
Adding a pipe: email=victim@email.com|email=attacker@email.com
Adding a cc mail: email="victim@email.com%0a%0dcc:attacker@mail.com"
Adding a bcc mail: email="victim@mail.com%0a%0dbcc:attacker@mail.com
Case 3: Manipulating JSON
Many times, the request parameters are passed as JSON objects to the reset password API. But the good news is, you can still do the same manipulations for JSON objects as well.
Some other things you can try with JSON:
Pass an array: {"email":["victim@mail.com","attacker@mail.com"]}
Pass wildcard operator in array: {"email":["victim@mail.com","*"]}
Pass null in array: {"email":["victim@mail.com","null"]}
Pass null: {"email":"null"}
Pass wildcard operator: {"email":"*"}
Note: JSON is funny, and different parsers work with JSON objects differently. For example, if two identical parameters are passed, Python may parse the second parameter first and Ruby may parse the first parameter first. This can result in interesting behavior across applications when we pass multiple parameters with the same key, or no parameters, or wildcards, etc.
And so, always try payloads like {"email":"*"} in every request that uses JSON and see how the application responds. Sometimes, wildcards or nulls on API endpoints can return highly sensitive data of all users, or error messages with sensitive info, etc.
Case 4: Token Reuse
Password reset tokens should expire once used. Users should not be able to use the same password reset links multiple times. While this cannot be exploited in real life unless you have some way of getting the victim's access token, but it is still not a good practice to implement password reset in one of the following ways.
Case 4.1:
- Request password reset for person@email.com
- Open the password reset link
- Reset the password
- Try to use the same link again to reset the password again
- If the link doesn't expire and same link can be used again, the web application is vulnerable.
Case 4.2:
- Request password reset for person@email.com
- Do not use the reset link. Keep the link say L1
- Request password reset for person@email.com again
- Do not use the new reset link L2 as well
- After requesting L2, now try using L1 to reset the password
L1 should not work. If L1 can still be used after requesting L2, the implementation of password reset is not secure as L1 should auto expire once L2 is requested.
Case 5: Password Reset Token Leakage via Referer header
The Referer header is an HTTP Request Header that helps web servers identify where request is coming from. When you make a request from webpage 1 to webpage 2, the Referer header is added to the request for webpage 2 to identify that the request came from webpage 1. This information is used for logging, caching and analytics.
However, this also means that requests originating from the password reset page may also leak the password reset token via the the Referer header. Sometimes, web applications have footers or sidebars with links to third party websites, links to the company's social media accounts, etc. And the Referer header may leak the token to these external links.
- Request password reset.
- Open the password reset link.
- Start Burp proxy and turn the intercept on.
- Click on any of the third party links/ social media links on the password reset page.
- You may see the password reset token being leaked in the Referer header.
Again, this is more of a misconfiguration than a security issue as you would normally only have links to trusted websites on your web page. But this is definitely worth mentioning as this is a high risk issue for CMS' and LMS' which allow you to customize what links appear on UI.
Case 6: Hidden Parameters
This is a rather rare case where on requesting the password reset token, the reset link does not have the email parameter on UI. It only asks the user for new password. But when you intercept the request through a proxy like Burp, you can see that there is a hidden parameter email that goes along with the password.
In this case, you can just edit the email parameter in Burp and reset password for any user's account. This works only when the web server just validates if the token is valid and does not check which email the token links to.
Case 7: 2FA Bypass
Many applications implement password reset by sending an OTP to the user's email. And only when the user enters the right OTP, the application redirects the user to the password reset page.
OTP is probably not the best way to implement password reset as there are many ways of bypassing OTPs. In case there is no rate limiting implemented by the web server, bruteforcing a 6 digit OTP is just a matter of minutes with a tool like Burp Intruder. There are many other ways of bypassing 2FA like response manipulation, IP Rotate to get past rate limiting, etc.
Case 8: Guessing
This involves guessing the password reset token after observing how the token is generated multiple times. This is quite time consuming and complicated but very effective if it works. There is no general methodology but you can try a few things to figure out how the token is built.
Ideally, you would create atleast 3 accounts on the website and request multiple password reset tokens from each account. Then you can start looking at patterns in all these password reset tokens.
Look at the password reset token for visual clues. Sometimes you can identify how the token was generated by just looking at the string. For example, if the token starts with 'ey' and is a long string separated by 2 dots in between, the token is most likely a JSON Web Token(JWT). Similarly, you can visually identify if the token is Base64 encoded, MD5 hash, etc by the token characters and length.
Sometimes, the token may contain the user ID for which password reset is requested. You may notice this after requesting the password reset token multiple times for the same account and observing if there is a fixed part. You may even see this in payload data of a JWT.
After multiple attempts of password reset, you might notice that the token has a fixed part and a variable part that changes after every attempt. At such times, it is important to investigate if the variable part is the timestamp. One way to do it is to request multiple password reset links at the same time instant by using something like Burp Intruder. If the tokens requested at the same time instant have the same variable part, it is probably the timestamp in some form. You might be able to use it to construct your own token even if you're not able to completely decode the timestamp format.
Figuring out the fixed part of the token is the most difficult. Typically, you would like to collect basic information of the user like email, first name, last name, user ID and try to construct the fixed part out of this. You can try encoding and hashing the data against MD4, MD5, SHA1, SHA256, etc. In case the token is a JWT, you can go to a site like jwt.io and try signing all this collected information with context specific keys like company name, etc.
This article will be updated as I come across new techniques.
For any questions, feel free to reach out on Twitter or Instagram
Until next time.