
Introduction
During a white-box security assessment of a Next.js application, I began reviewing the source code to understand how the password reset functionality was implemented. While analyzing the logic, I noticed that the application generated password reset tokens using only the user’s email address and the current Unix timestamp. Since both of these values are either known or can be derived by an attacker, the reset token becomes predictable. This immediately raised a security concern because a predictable reset token can allow an attacker to reset another user’s password without accessing their email account.
Source Code Analysis
While reviewing the source code, I observed that the application generates the password reset token using the following logic:
const now = newDate();
const timestamp = Math.floor(now.getTime()/1000);
const token = hashMD5(email+timestamp);

At first glance, this implementation may appear acceptable, but a closer look reveals a serious security flaw.
The application converts the current time into a Unix timestamp and concatenates it with the user’s email address. The resulting string is then hashed using the MD5 algorithm to generate the password reset token.
This means the reset token is entirely derived from two values:
- User’s email address
- Current Unix timestamp
Neither of these values is secret.
The email address is generally easy to obtain or guess, especially in applications where usernames or email addresses are publicly known. More importantly, the application returns the exact request time (requestedAt) in its response after initiating the password reset process. This timestamp can easily be converted into a Unix timestamp, allowing an attacker to reproduce the exact input used to generate the reset token.
As a result, instead of relying on a cryptographically secure random token, the application generates a deterministic token. Anyone who knows the victim’s email address and the corresponding timestamp can calculate the same password reset token without having access to the victim’s mailbox.
Transition
Now that we understand how the token is generated, let’s trigger the Forgot Password functionality and collect the information required to reproduce the reset token.
Walkthrough
The first step is to initiate the password reset process for a valid user account. In this case, I used the email address alice@example.com and submitted a password reset request through the application’s Forgot Password page.

Reset token has been sent to the user email.

Now, let’s inspect the HTTP history in Burp Suite to analyze the request and response. Here, we can observe the requestedAt timestamp returned by the application.

I created a small JavaScript script to convert the requestedAt value into a Unix timestamp.

Run the script using the following command:
cmd:- node timestamp.js

Now, generate the password reset token by calculating the MD5 hash of the user’s email address concatenated with the generated Unix timestamp.
cmd:- echo -n "alice@example.com1785144679" | md5sum

I noticed that the password reset page was either not implemented correctly or was not functioning as expected. Therefore, I had to find another way to reset the password.

Since the browser wasn’t allowing me to proceed, I sent the same request to Burp Suite Repeater and replayed it against the reset password endpoint. As expected, the application returned 405 Method Not Allowed, indicating that the endpoint does not accept GET requests

I then changed the request method from GET to POST and sent the request again. This time, instead of 405 Method Not Allowed, the server responded with Invalid JSON body, indicating that the endpoint expects a JSON payload rather than a standard request. The next step was to provide the required parameters in JSON format.

Even after supplying the token in the JSON body, the application returned the same error. This indicated that the endpoint required additional parameters. I then added the password field with a new password and resent the request.

Success! Once both the token and the new password were included in the JSON body, the application successfully reset the user’s password.

Bonus!
Since we were able to reset a regular user’s password, the same approach should also work for the administrator account. Let’s try resetting the admin password using the same technique.
I initiated the password reset process for the administrator account by submitting a password reset request for admin@0ss.com through the Forgot Password page


Similar to the previous attack, the application returns the requestedAt timestamp in the response. This value will be used to recreate the administrator’s password reset token.

Using the same token generation process demonstrated earlier, I recreated the administrator’s password reset token. After supplying the generated token and a new password, the application successfully reset the administrator’s password.

To verify that the attack was successful, I attempted to log in using the administrator account with the newly set password.

We successfully login admin user, and we got the another flag also.

Remediation
To prevent this vulnerability, password reset tokens should be generated using a cryptographically secure random value instead of predictable inputs such as the user’s email address and timestamp. This ensures that attackers cannot recreate or guess valid reset tokens.
Additionally, the application should avoid exposing internal values such as the requestedAt timestamp in the response, as this information can assist attackers in reproducing reset tokens. Implementing rate limiting on the password reset endpoint can further reduce the risk of automated attacks and token generation attempts.
You should also:
- Use
crypto.randomBytes()(or an equivalent cryptographically secure random number generator) to generate reset tokens. - Store only a hashed version of the reset token in the database.
- Set a short expiration time for reset tokens and invalidate them immediately after successful use.
Credit and Source: https://github.com/kOaDT/oss-oopssec-store

