Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 96 additions & 5 deletions src/pentesting-web/reset-password.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

- Attackers may manipulate the Host header during password reset requests to point the reset link to a malicious site.
- **Impact**: Leads to potential account takeover by leaking reset tokens to attackers.
- **Exploitation tips**:
- Test not only `Host`, but also override headers such as `X-Forwarded-Host`, `Forwarded`, `X-Host`, and `X-Original-Host`. Reverse proxies and middleware sometimes build the reset URL from those values instead of from the canonical host.
- If the reset request contains parameters such as `baseurl`, `return_to`, `redirect_uri`, `redirect_url`, `next`, or a tenant/domain selector, point them to an attacker-controlled host and inspect the email template.
- Try the poisoning payload on the first request **and** on "resend reset link" endpoints. In several real cases only one of the two paths was vulnerable.
- **Mitigation Steps**:
- Validate the Host header against a whitelist of allowed domains.
- Use secure, server-side methods to generate absolute URLs.
Expand Down Expand Up @@ -80,12 +84,33 @@ email="victim@mail.tld",email="attacker@mail.tld"
```php
POST /resetPassword
[...]
{"email":["victim@mail.tld","atracker@mail.tld"]}
{"email":["victim@mail.tld","attacker@mail.tld"]}
```

- Convert a normal form submission to JSON and try nested arrays/objects (some frameworks type-coerce a single-recipient field into multiple recipients)

```json
{
"user": {
"email": ["victim@mail.tld", "attacker@mail.tld"]
}
}
```

- Try framework-specific multi-value formats

```http
POST /resetPassword HTTP/1.1
Content-Type: application/x-www-form-urlencoded

email[]=victim@mail.tld&email[]=attacker@mail.tld
```

- Also try comma-separated values inside a single string (`victim@mail.tld,attacker@mail.tld`) and content-type conversions (`x-www-form-urlencoded` -> JSON, `multipart/form-data` -> JSON) because some mailers or validators split recipients late in the pipeline.
- **Mitigation Steps**:
- Properly parse and validate email parameters server-side.
- Use prepared statements or parameterized queries to prevent injection attacks.
- Reject arrays / repeated parameters when a single recipient is expected.
- Cast the final recipient to a string before passing it to the mailer and re-validate after any normalization step.
- **References**:
- [https://medium.com/@0xankush/readme-com-account-takeover-bugbounty-fulldisclosure-a36ddbe915be](https://medium.com/@0xankush/readme-com-account-takeover-bugbounty-fulldisclosure-a36ddbe915be)
- [https://ninadmathpati.com/2019/08/17/how-i-was-able-to-earn-1000-with-just-10-minutes-of-bug-bounty/](https://ninadmathpati.com/2019/08/17/how-i-was-able-to-earn-1000-with-just-10-minutes-of-bug-bounty/)
Expand Down Expand Up @@ -125,10 +150,15 @@ POST /api/changepass
- Based on Firstname and Lastname
- Based on Date of Birth
- Based on Cryptography
- Also request several reset links in parallel and compare them. Identical tokens, deterministic increments, or "one-time" links that remain valid after first use usually indicate a race condition or a state machine bug.
- **Mitigation Steps**:
- Use strong, cryptographic methods for token generation.
- Ensure sufficient randomness and length to prevent predictability.
- **Tools**: Use Burp Sequencer to analyze the randomness of tokens.
- **Tools**: Use Burp Sequencer to analyze the randomness of tokens, and Turbo Intruder / Burp Repeater group-send to test parallel issuance and reuse.

{{#ref}}
race-condition.md
{{#endref}}

## **Guessable UUID**

Expand All @@ -155,9 +185,22 @@ uuid-insecurities.md
## **Using Expired Token**

- Testing whether expired tokens can still be used for password reset.
- Check both the final reset endpoint and any intermediate "validate token" endpoint. Some applications reject the token on the UI step but still accept it on the JSON/API step that actually changes the password.
- **Mitigation Steps**:
- Implement strict token expiration policies and validate token expiry server-side.

## **Race / Reuse of One-Time Reset Tokens**

- Some applications invalidate reset tokens only after the password update finishes, or only in one worker/process. This makes supposedly one-time links reusable when the same token is submitted concurrently.
- Practical checks:
- Open the same reset link in two browsers and submit both almost at the same time.
- Send two `POST /reset` requests in parallel with the same token but different passwords.
- Complete the password change once, then replay the exact final request before following any redirect chain.
- If two different passwords are accepted or the same token works twice, you likely found a race condition in token invalidation.
- **Mitigation Steps**:
- Invalidate the token atomically before or during the password update transaction.
- Ensure the token is single-use across all workers and replicas.

## **Brute Force Password Reset Token**

- Attempting to brute-force the reset token using tools like Burpsuite and IP-Rotator to bypass IP-based rate limits.
Expand All @@ -168,16 +211,63 @@ uuid-insecurities.md
## **Try Using Your Token**

- Testing if an attacker's reset token can be used in conjunction with the victim's email.
- This usually appears when the application validates the token and the target account independently. Typical vulnerable patterns:
- Step 1 (`/forgot-password`) issues a token tied to the attacker account.
- Step 2 (`/reset-password`) accepts both `token` and `email` / `userId` / `username` from the client.
- The backend checks that the token exists, but uses the attacker-controlled identifier to decide **which** password to change.
- Practical mutations to test:
- Keep your valid token but replace `email`, `userId`, `username`, `login`, or `accountId` with the victim's value.
- Move the token between query string, JSON body, and headers while changing the victim identifier in the other location.
- If the reset page first calls a "validate token" endpoint and then a separate "change password" endpoint, change the victim identifier only in the second request.
- **Mitigation Steps**:
- Bind the token to the account on the server side and derive the target user exclusively from that token.
- Do not trust any account identifier submitted alongside the token unless it matches the token owner.

## **Password Reset Token Disclosure in API Responses**

- Some APIs return the reset token (`resetToken`, `tempToken`, `recoveryCode`) directly in the forgot-password response or in a secondary polling/debug endpoint.
- This is usually an immediate ATO: trigger a reset for the victim, capture the token from the API response, then call the final reset endpoint without mailbox access.
- Also inspect GraphQL responses, mobile APIs, websocket notifications, batch endpoints, and verbose error messages for leaked token values or token expiry metadata.

```http
POST /api/v1/account/forgot-password HTTP/1.1
Content-Type: application/json

{"email":"victim@example.com"}

HTTP/1.1 200 OK
Content-Type: application/json

{"tempToken":"4f89...","tokenExpiry":"<expiry-timestamp>","user":{"email":"victim@example.com"}}
```

## **Missing "token was generated" check**

- A subtle variant appears when the reset endpoint compares the attacker-supplied token with the value stored in the database, but never verifies that a reset flow was actually started.
- If the default stored value is `NULL` or an empty string, try sending `null`, `""`, omitting the field entirely, or using the string `"null"`.
- This bug is especially interesting on recently created accounts or on accounts where the application initializes `tokenExpiry` during account creation instead of during the reset flow.

```json
{
"user": {
"email": "victim@example.com",
"tempToken": null,
"password": "NewP@ssw0rd!"
}
}
```

- **Mitigation Steps**:
- Ensure that tokens are bound to the user session or other user-specific attributes.
- Reject reset attempts unless the server recorded a live reset request for that account.

## **Session Invalidation in Logout/Password Reset**

- Ensuring that sessions are invalidated when a user logs out or resets their password.
- **Mitigation Steps**:
- Implement proper session management, ensuring that all sessions are invalidated upon logout or password reset.

## **Session Invalidation in Logout/Password Reset**
## **Reset Token Expiration**

- Reset tokens should have an expiration time after which they become invalid.
- **Mitigation Steps**:
Expand Down Expand Up @@ -307,5 +397,6 @@ Content-Type: application/json
- [https://anugrahsr.github.io/posts/10-Password-reset-flaws/#10-try-using-your-token](https://anugrahsr.github.io/posts/10-Password-reset-flaws/#10-try-using-your-token)
- [https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/](https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/)
- [How I Found a Critical Password Reset Bug (Registration upsert ATO)](https://s41n1k.medium.com/how-i-found-a-critical-password-reset-bug-in-the-bb-program-and-got-4-000-a22fffe285e1)

- [GitLab Critical Security Release: 16.7.2, 16.6.4, 16.5.6](https://docs.gitlab.com/releases/patches/patch-release-gitlab-16-7-2-released/)
- [Critical: Unauthenticated Password Reset Token Disclosure Leading to Account Takeover in Flowise Cloud and Local Deployments](https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-wgpv-6j63-x5ph)
{{#include ../banners/hacktricks-training.md}}