Exploring Password Reset Vulnerabilities and Security Best Practices

Passwords are still the most common way of authenticating a user. However, setting up a password management system that is both simple and secure can sometimes be tricky.

Indeed, the password reset feature is an interesting target for an attacker, as it can facilitate the theft of user accounts if poorly implemented.

In this article, we’ll look at the common errors that can be found in this type of functionality, the possible exploits and ways to protect yourself. But first, let’s take a look at how password resets generally work.

How Does Password Reset Work?

The password reset feature is an essential mechanism for managing user accounts on web platforms. It enables users to regain access to their account when they have forgotten their password. Most of the time, this functionality is accessible via a “Forgot password” link or button on the authentication page.

When a user requests to reset their password, the system usually generates a unique link that is sent to the email address associated with the account. This link takes the user to a page where he or she can enter a new password. And for security reasons, this link is often valid for a limited period.

On the developer side, the standard implementation is to ensure that the process is secure. The best practice is to use reset tokens, which are unique strings of characters, often encrypted to ensure that the reset request is legitimate.

In addition, measures are often taken to prevent brute force attacks or reset request spam, such as limiting the number of authorised requests or implementing captchas.

Exploiting Password Reset Vulnerabilities

Host Header Poisoning is an attack technique that exploits the vulnerability of a web application by relying on the value of the Host header of the HTTP request.

Usually, this header indicates the domain targeted by the request, but sometimes it can be manipulated by an attacker. This attack could allow the reset email address to be hijacked and sent to a server that an attacker controls, but could also modify the base domain of the reset link in the email, enabling the victim to be redirected to a third-party website, in order to carry out a phishing attack and recover the login credentials as well as stealing the victim’s reset token.

Another exploit could be the fact that some of the server’s JavaScript resources are based on the host header. If an attacker is able to modify its value, then the links to the JavaScript files will be modified enabling third-party resources to be loaded.

Exploit example

A password reset request is made by targeting the target’s email address.

If the application generates a reset link using the Host header without validation, the attacker could redirect the email to a malicious server.

This will enable him to steal the account.

POST /forgot-password HTTP/2
Host: attacker.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 191
Te: trailers
Connection: close
csrf=qxoCDf28hFqnF2xP6HtGiIhnDFHmryPm&username=carlos

Response:

GET /forgot-password?temp-forgot-password-token=okxkbxb8qpq351vx7nsvrzh1rpq0exd7 HTTP/1.1
Host: attacker.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Length: 191
Te: trailers
Connection: close

How to prevent Host Header Poisoning vulnerabilities?

To address the Host Header Poisoning vulnerability, particularly in Host header-based password reset processes, it is essential to adopt a multi-layered approach to security.

The first step is to implement strict validation of the host header. This involves configuring the application to accept only known and valid host headers, by comparing the host header of each request with a whitelist of approved hosts. This helps to ensure that the application will only respond to requests from legitimate domains.

For password reset links themselves, it is important to use secure and unique reset tokens. These tokens should be designed to be valid only for a short period and uniquely associated with the user concerned. Even if an attacker intercepts a reset link, the token’s rapid expiry and uniqueness reduce the risk of exploitation.

In the context of token security, particularly those used in password reset procedures, the absence of an expiration delay can be a major flaw. A non-expiring token can potentially be leaked and reused by an attacker, compromising the security of user accounts. The mechanisms by which a token can leak are varied.

For example, web archiving services, such as WebArchive, can unintentionally or deliberately save versions of web pages containing unexpired tokens. In addition, the “referer” header in HTTP requests can accidentally leak the token to third-party sites, especially if these are compromised.

Given these vulnerabilities, implementing an expiration delay for tokens is a crucial security measure. This is particularly relevant in a context where even less obvious leakage vectors, such as web archiving, can pose a real risk. Admittedly, token leakage via the “referer” header can be exploited almost instantly, making expiration less effective against this specific type of attack. However, an expiration delay significantly reduces the window of opportunity for an attacker, particularly in slower exploitation scenarios.

For password reset tokens, it is recommended to set an expiration delay of no more than one hour after they are generated. An even shorter period, such as 20 minutes, is often recommended to minimise risk. In addition, it is vital that the token is designed to be used only once. After use, the token must be invalidated, preventing any attempt by an attacker to re-use it. This uniqueness ensures that even if a token is intercepted, it cannot be used to carry out repeated attacks or to access an account after the legitimate owner has already used the token to change his/her password.

Some applications rely on user-controllable parameters to build password reset links.

This implementation can allow malicious users to hijack the reset links, enabling them to steal user accounts.

Let’s look at a use case where we were able to poison the host of a reset link via a JSON parameter in the request.

Exploitation

During a white box penetration test, our team discovered an application generating the password reset link using a request parameter that could be manipulated by the user.

The following request was sent to the server:

POST /reset-password HTTP/1.1
Host: redacted.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 191
Te: trailers
Connection: close
{"email":"[email protected]","baseurl":"redacted.com"}

There is a “baseurl” parameter which will be used to build the password reset url. The first thing that might come to mind is to change the value of the “baseurl” parameter to a domain name that the attacker can control so that the link points to the wrong domain.

However, during the security audit, we realised that the server was not accepting the request with a third-party domain and that validation is carried out on the server side.

By inspecting the application’s source code (white box conditions), we found that the validation was based on the use of the urlparse function in the urllib.parse module.

The application checked that the domain name corresponded to that of the application based on the hostname attribute of the urlparse function.

After some research, we came across a security report which indicates that it is possible to confuse the parser’s validation thanks to the following payload:

attacker.com\@serveur_dorigine.com

All an attacker has to do is make a reset request with the following query:

POST /reset-password HTTP/1.1
Host: redacted.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 191
Te: trailers
Connection: close
{"email":"[email protected]","baseurl":"attacker.com\@serveur_dorigine.com"}

The victim will receive the following email:

Once the victim clicks on the link, his/her token will be sent to the attacker’s server, which he/she can then use to steal the account.

Fixing the vulnerability

It is essential not to rely solely on the Host header value and user-controllable parameters to generate password reset links in web applications for several reasons relating to the security and integrity of the application.

Instead, the application should use a pre-configured, server-side verified base URL (whitelist), ensuring that the reset link always leads to a legitimate domain. Next, it is important to implement a system of single-use reset tokens, which expire after a short period of time. These tokens must be cryptographically secure and stored securely.

When a password reset request is made, a random token is generated to verify the integrity of the user using it. This token must be complex because, if an attacker is able to understand the token generation mechanism and predict it, he/she could generate other valid tokens and thus steal user accounts.

There are many scenarios to consider, and we will use the case of UUIDv1 to illustrate our point.

A UUIDv1 is a specific version of an identifier generated using the current time and MAC address of the computer generating it. UUIDv1s are generated by combining a timestamp, the MAC address of the machine, and a unique identifier.

Here’s an example:

Exploitation

The “Sandwich” attack targets UUIDv1s by exploiting their predictability. When two UUIDv1s are generated over a short period, only the first four timestamp bytes differ.

The next stage of the attack consists in reconstructing the unknown UUID using a brute force attack on part of the timestamp.

Let’s take the example of an attacker trying to steal the account of a user with the email “[email protected]“.

First, an attacker will create a user account on the target platform. Next, he will initiate a password reset request in the following order:

The aim is to minimise the delay between the three requests in order to reduce the load on the brute force that will be carried out afterwards. To achieve this, the Burp Suite “send group” feature can be used.

Following these requests, the attacker will receive the following UUIDs:

UUID 1: 6b894ab2-845d-11ee-8227-00155d4e2cec
UUID 3: 6b89a4c6-845d-11ee-8227-00155d4e2cec

It’s easy to see that only a few characters differ between the two identifiers (the timestamp part).

You can then differentiate between the timestamps to estimate the number of attempts needed to find the victim’s token, which is somewhere between the two.

6b89a4c6 - 6b894ab2 = 23060

It will therefore take just over 23,000 attempts to find the victim’s token. Burp Suite’s Intruder feature can be used to test all possibilities and determine the value of the victim’s valid token.

At the end of the brute force, we determine that the UUID 6b897a32-845d-11ee-8227-00155d4e2cec is the valid one. With this token, the attacker can reset the victim’s password and steal the account.

This attack is not only effective for UUIDv1s, but for all token generations that use the server’s timestamp as a random number during generation.

Remediation

One recommended approach is to use robust tokens such as UUIDv4. This version of UUID generates unique identifiers more randomly, offering higher entropy than other methods.

Password reset tokens should not be determinable. It is therefore important to ensure that they have a long enough but above all random value each time they are generated, to avoid being usable to determine other tokens.

Author: Yacine DJABER – Pentester @Vaadata