Attacking weak password reset implementation

In this blog post, I detail how I uncovered a critical vulnerability in a popular online service’s password reset feature. Due to poor password reset implementation practices, I was able to gain unauthorized access to user accounts. In this research, I will lay out the thought process from a hacker’s perspective to achieve an account takeover. I am not allowed to reveal the service’s identity, so we will call the vulnerable site site.com throughout the research.

Technical Analysis

site.com allows users to reset passwords using the forgot password feature. Users who have lost their login password can request a unique link to reset the password using their email address. Once a user has asked for a reset password link they can visit the link received via email and set a new password.

The reset password links are in this format:

https://www.site.com/tracking/{{unique_hash_1}}/{{unique_hash_2}}

example reset password link:

https://www.site.com/tracking/6658085a4557ad8/6659c0b412fd5469

Once a GET request to this link is sent, the response contains the actual password reset link as a part of the Location header which is in the format of:

https://www.site.com/account/updatePassword/{{unique_password_rest_token}}

Now, if an attacker gets the correct tracking link they can find the password reset URL for the victim and takeover the victim’s account.

The tracking link contains two random hash values which are actually a value in the hex format. Also, if we look at the multiple tracking links requested in the nearby time the hex values seem like they are sequential and not random.

The randomness in tracking links and how to brute force it

Let’s take a look at multiple tracking links requested for different accounts within almost the same second time.

https://www.site.com/tracking/65c2e8db4557ad4/65c4e8f812fd5465
https://www.site.com/tracking/65c2eb504557ad5/65c4e78812fd5466
https://www.site.com/tracking/65c43aa64557ad6/65c5a68912fd5467
https://www.site.com/tracking/65c762584557ad7/65c9972112fd5468
https://www.site.com/tracking/6658085a4557ad8/6659c0b412fd5469
https://www.site.com/tracking/665cb6064557ad9/665ea6a412fd546a

Let’s extract hash 1 from the above links and see how random the values are:

65c2e8db4557ad4
65c2eb504557ad5
65c43aa64557ad6
65c762584557ad7
6658085a4557ad8
665cb6064557ad9

If we observe these numbers they are not random but in ascending order. To make it easier we can even convert them to decimal numbers by using any hex-to-decimal converter.

      HEX       -       DECIMAL 
65c2e8db4557ad4 - 458292448235715284
65c2eb504557ad5 - 458292617081617109
65c43aa64557ad6 - 458315661191772886
65c762584557ad7 - 458371165591010007
6658085a4557ad8 - 460915848351415000
665cb6064557ad9 - 460998151735966425

This also gives us a hint on which account the reset password link was requested first and how the sequence was followed.

Let’s assume, the email address associated which each of these tracking links was in this order:

email    - account1@attacker.com
pw reset - https://www.site.com/tracking/65c2e8db4557ad4/65c4e8f812fd5465
email    - account2@attacker.com
pw reset - https://www.site.com/tracking/65c2eb504557ad5/65c4e78812fd5466
email    - victim@victim.com
pw reset - https://www.site.com/tracking/65c43aa64557ad6/65c5a68912fd5467
email    - account3@attacker.com
pw reset - https://www.site.com/tracking/65c762584557ad7/65c9972112fd5468
email    - account4@attacker.com
pw reset - https://www.site.com/tracking/6658085a4557ad8/6659c0b412fd5469
email    - account5@attacker.com
pw reset - https://www.site.com/tracking/665cb6064557ad9/665ea6a412fd546a

Meaning, the attacker made the password reset request for attacker-controlled accounts and a victim account in a short period of time (fraction of second) then the attacker can actually identify the missing link by observing the hash 1 in the received tracking links.

In the above example, it was evident that the Hash-1 between ...ad5 and ...ad7 was missing for the tracking link his accounts received to reset the password. Meaning, the victim must have received a tracking link between these two value.

This is how an attacker can actually map the range of hash-1. For hash-2, The hash-2 is also sequential and it is also easier to find a range as it will be in the range of hash-2 of hash-1 such as in the above example hash-2 of the link with hash-1 ...ad5 and hash-2 of the link with hash-1 ..ad7.

In the above example,

The range of hash-1 in hex - [65c2eb504557ad5 - 65c762584557ad7]
The range of hash-2 in hex - [65c4e78812fd5466 - 65c9972112fd5468]

An attacker can brute force this range to find the password reset link. However, there are two ranges meaning for each value in hash-1 the attacker needs to map all the values of hash-2 which makes a total combination value which no modern computer can brute force.

However, if we look closely we can actually reduce the range by finding which placeholder in the given hex values is random and what are the static values. Also, is there any sequence which we can guess? Let’s see:

For predicting hash-1:

[Static Values] [Random Values] [Sequence]
65c              2eb50           4557ad5
65c              xxxxx           4557ad6
65c              76258           4557ad7

This drastically reduces the total combinations as now the range for hash-1 becomes:

[2eb50 - 76258] (in hex)
[191312 - 483928] (in decimal) 

Making total combinations as 483928 – 191312 = 292616

But again, if the total combinations of hash-1 is around 300000 and for hash-2 if it is also around 300000 again it is almost impossible to brute force 300000^300000 combinations.

Upon further testing, I identified that Hash-1 is actually not being validated by the backend and only Hash-2 is attached with the password reset link. Meaning, Hash-1 can be any valid Hash-1 value and Hash-2 has to be the right value received by the victim.

We can now again reduce the range of total bruteforce attempts.

Let’s investigate hash-2 from the above example:-

[Static Values] [Random Values] [Sequence]
65c              4e788           12fd5466
65c              xxxxx           12fd5467
65c              99721           12fd5468

The static value should be 65c and the last sequence has to be 12fd5467. We are left with random values to guess. The range for this is:

[4e788 - 99721] (in hex)
[321416 - 628513] (in decimal) 

Making total combinations as: 628513 – 321416 = 307097

These many combinations can easily be brute-forced. I have written a Python script below that can generate all the possible hash-2 values given the static value, the range of random values, and the sequence that was guessed.

The python script is as below:

import os

directory = os.getcwd()

static_value = input("What is the static value in the hash?\nYour Answer:")
start_hex = input("What is the lowest or starting number of the range?\nYour Answer:")
end_hex = input("What is the highest or ending number of the range?\nYour Answer:")
sequence = input("What is the ending sequence of the hash?\nYour Answer:")

start_decimal = int(start_hex, 16)
end_decimal = int(end_hex, 16)

print("Generating all the possible hex value...")

with open(directory+'/hash-2','w') as file:
    for decimal in range(start_decimal, end_decimal+1):
        hex_value = hex(decimal)
        file.write(static_value + hex_value.replace('0x', '') + sequence)
        file.write("\n")

print("File Generated and saved at " + directory + "/hash-2")

saved this file as hash_2_generator.py and ran it with: python3 hash_2_generator.py

The output (All the inputs are of hex values):

What is the static value in the hash?
Your Answer:65c
What is the lowest or starting number of the range?
Your Answer:4e788
What is the highest or ending number of the range?
Your Answer:99721
What is the ending sequence of the hash?
Your Answer:12fd5467
Generating all the possible hex value...
File Generated and saved at ..../hash-2

The output file:

A quick search with the value 65c5a68912fd5467 (which is the actual hash of the victim’s tracking link) was found at 48898 positions, meaning that after 48898 attempts, the attacker could have found the victim’s reset password link.

To make this attack worse and compromise more than one victim account, an attacker can place many other victims’ email addresses between the attacker’s emails. While brute-forcing, an attacker will find the password reset links of all the victim accounts they want to target.

I reported this finding to the specific vulnerable site, and I am pleased to share that they have since resolved the issue.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *