Password reset poisoning

What is password reset poisoning?

A password reset poisoning vulnerability happens when a web application uses the Host header of an HTTP request to create password reset links. This allows an attacker to change a victim’s password and take control of their application account. Password reset poisoning attacks are often considered a type of Host header attack.


Severity: medium severity
Prevalence: discovered rarely
Scope: web applications with password reset functionality
Technical impact: account takeover
Worst-case consequences: sensitive information breach
Quick fix: do not take domain names for reset links from Host headers

How does password reset functionality work?

Most password reset mechanisms in web applications work by sending a password reset link to an email address supplied by the user. The user then clicks on the emailed link to confirm their identity and can proceed to change the password.

For example, if the user kosh@invicti.com has an account in the vulnweb.com application and forgets their password, they can enter their email address in a special password reset request form. The application then checks if there is a registered user with this email in its database. If so, it sends a link with a one-time token to the provided address.

The user then opens the email, clicks on the link to a reset page on vulnweb.com, and the web application receives the token in the GET request. If the token matches the emailed one, the application displays a password change form.

How do password reset poisoning vulnerabilities happen?

The above mechanism seems secure but actually includes a typical application security weakness. Unless the web application is designed to be hosted on a static domain, it has to know what application base URL to use. When creating a password reset mechanism, an easy way to construct the reset URL is to take the domain name from the Host header of the original HTTP request. After all, if an application is hosted at example.com, it should be safe to assume that web browsers will only send HTTP requests with a Host header specifying example.com. If there was no way to tamper with Host header values, you could indeed use them to dynamically construct reset links that always specify the correct host.

Unfortunately, the Host header in an HTTP request can be modified by the sender or even manipulated in transit through Host header injection. This allows a malicious hacker to construct a link that leads to a website under their control. When a user clicks the link, the attacker receives the secret one-time token and can use it to reset that user’s password.

Related password reset vulnerabilities

In addition to password reset poisoning, web applications may have other minor vulnerabilities related to password reset functionality. For example, if the application displays different HTML pages for registered and unregistered addresses, attackers could abuse this to perform brute-force user enumeration (though these can be easily prevented by rate limiting). You can read more about other, less common attacks related to password reset functionality as well as general cybersecurity issues on InfoSec Writeups.

Example of a password reset poisoning attack

Let’s go through an example where the web application is hosted on vulnweb.com, the user who forgot the password is called Kosh Naranek, and the attacker controls a server on shadow.example.com.

First, the attacker finds the victim’s email address, which is kosh@invicti.com. They can find this address on the company website, through social engineering attacks, or using other methods.

Then, the attacker creates the following GET request directed to the vulnweb.com password reset page:

GET https://vulnweb.com/forgot-password.php?email=kosh@invicti.com HTTP/1.1
Host: shadow.example.com

If this was a legitimate request, the Host header value would be the same as the domain in the URL included in the GET request. Note that you can do the same thing using a POST request instead, if required by the web application.

Next, the vulnweb.com application receives the request. The application code is vulnerable to poisoning because it takes the domain name from the Host header:

$token = bin2hex(random_bytes(16)); // random token; in this example, 2403e128e40a35bb535e9da01f0faf7b
$resetPasswordURL = "https://{$_SERVER['HTTP_HOST']}/reset.php?token=$token";

Now, the web application creates a password reset email that includes a link built using the $resetPasswordURL variable. The email is sent to the correct address, kosh@invicti.com, but the link leads to shadow.example.com instead of vulnweb.com.

If Kosh fails to notice that the domain in the link is different to the web application domain, they will click on the link, and their browser will send a GET request to the attacker-controlled server at shadow.example.com. The attacker can further increase the chances of success using techniques such as HTTP spoofing.

Since the attacker controls shadow.example.com, they can make the page look just like the real one to avoid arousing the victim’s suspicion. They might also decide to display a fake error message or even build shadow.example.com to act as a proxy for the real application.

In any case, the attacker now has the original reset token sent to the shadow.example.com backend, so they can make a GET request to the legitimate host:

GET https://vulnweb.com/reset.php?token=2403e128e40a35bb535e9da01f0faf7b HTTP/1.1

The token is valid, so the vulnweb.com web application assumes this is the original user and displays a password change form. The attacker can now input their own new password, taking control of the user’s account.

How to detect password reset vulnerabilities?

The best way to detect password reset vulnerabilities depends on whether they are already known or unknown.

  • If you only use commercial or open-source web applications, it may be enough to identify the exact version of the application you are using. If the identified version is susceptible to password reset poisoning, you can assume that your installation is vulnerable. You can identify the web app version manually or use a suitable security tool, such as a software composition analysis (SCA) solution.
  • If you develop your own web applications or want the ability to potentially find previously unknown password reset vulnerabilities (zero-days) in known applications, you must be able to successfully exploit the vulnerability to be certain that it exists. This requires either performing manual penetration testing with the help of security researchers or using a vulnerability scanner tool that can automatically exploit web vulnerabilities. Examples of such tools are Invicti and Acunetix by Invicti. We recommend using this method even for known vulnerabilities.

How to prevent password reset poisoning?

As a web application developer, never trust the content of the Host header or any other request headers that could be manipulated, including Content-type, Referer, and X-Forwarded-Host. If you need to use these values, always perform careful validation. This will help you prevent not just password reset poisoning but other attacks as well.

The safest way to construct password links is by specifying the application base URL in a safe configuration file. For example, you could create and use the following config.ini file:

[GLOBAL]
DB_URL=mysql://
DB_USER=mysql
DB_PASS=bLSy8r632eXgZBTC
[APPLICATION]
BASE_URL=vulnweb.com

This base URL can then be used safely as a constant in your code. To retrieve it, you can use a utility function, for example, get_config(section, entry):

$token = bin2hex(random_bytes(16));
$resetPasswordURL = "https://" . get_config('APPLICATION', 'BASE_URL') . "/reset.php?token=$token";

How to mitigate password reset poisoning?

The best way to mitigate potential password reset poisoning is by requiring multi-factor authentication functionality for all your web applications. This way, even if there is a password reset vulnerability, password reset tokens will not be enough to perform a successful attack.

If multi-factor authentication is not an option, you can use security questions as an additional password function involved in the reset process.

Frequently asked questions

What are password reset poisoning attacks?

Password reset poisoning is a web security issue where a web application generates password reset links in an unsafe way. This allows an attacker to access the secret reset token and change the victim’s password. Password reset abuse is considered a type of Host header attack.

 

Read more about Host header attacks.

How dangerous are password reset vulnerabilities?

Abusing password reset functionality allows attackers to gain access to web application user accounts. The consequences depend on the functionality of the web application and the permissions of the victim’s account. For example, in a financial application that stores sensitive information, a successful attack could have serious repercussions, such as credit card numbers being exposed. Access to a web application account could also let the attacker attempt privilege escalation, which could lead to further attacks against the web server and more.

 

Learn about privilege escalation, privilege elevation, and its consequences.

How to detect and prevent password reset poisoning?

Password reset poisoning is easy to detect and prevent. Password reset vulnerabilities can be easily detected using both automated vulnerability scanning and pentesting. To prevent password reset poisoning, never use data from HTTP headers or other user input to create password reset links. The best practice is to have base URLs for reset links included in static configuration files.

 

Learn more about application security practices that help avoid attacks such as password reset poisoning.

ClassificationID
CAPEC50
CWE640
WASC49
OWASP 2021A7


Written by: Tomasz Andrzej Nidecki, reviewed by: Benjamin Daniel Mussler