Respuesta rápida
OAuth añade superficie de ataque: cada paso (authorize, callback, token exchange) tiene su propia clase de bug. Las cuatro vulns más explotadas: state parameter ausente (CSRF en account linking → ATO), redirect_uri mal validado (token leak), response_type confusion (code/token enviado a dominio incorrecto), y storage de tokens en URL (filtración por Referer/cache). Bounties típicos: €1.000-€8.000 según severidad.
El flujo OAuth (refresher)
[Cliente] [Authorization Server] [Resource Server]
| | |
| /authorize?client_id=... | |
| &redirect_uri=... | |
| &state=... | |
| &response_type=code | |
| ─────────────────────────> | |
| | |
| 302 redirect_uri?code=... | |
| &state=... | |
| <───────────────────────── | |
| | |
| POST /token | |
| code + client_secret | |
| ─────────────────────────> | |
| | |
| access_token + refresh | |
| <───────────────────────── | |
| | |
| GET /api (Bearer token) |
| ────────────────────────────────────────────────────────> |
Cada flecha tiene un punto de fallo posible.
1. State parameter — la mitigación CSRF de OAuth
state es un valor aleatorio único que el cliente genera y verifica al volver. Sin state:
- Atacante inicia un flow OAuth con su cuenta del IdP.
- Captura su propio
codeen el callback. - Hace que la víctima visite
https://target.tld/oauth/callback?code=<atacante_code>. - El target intercambia el code → recibe el access_token del atacante → vincula la cuenta del atacante a la sesión de la víctima.
Resultado: la víctima está ahora "logueada como atacante" en su cuenta del target → atacante también tiene acceso, y puede ver lo que la víctima haga, o si el flow es de "link account", la víctima ha vinculado su cuenta a la del atacante (ATO si el atacante puede luego loguearse via el IdP).
Bypasses cuando state existe pero está mal hecho
- State predecible (timestamp, hash de IP). Si lo puedes reproducir, no protege.
- State no validado server-side. Si el cliente lo envía pero el servidor solo verifica que esté presente (no que coincida con el originalmente generado), bypass.
- State omitido y aceptado. Removerlo del callback URL → ¿el server lo procesa igual?
2. redirect_uri bypass — el más común
El redirect_uri debe estar registrado/validado por el authorization server. Si la validación es laxa, el code/token se redirige al dominio del atacante.
Bypasses típicos
# Original registrada: https://app.target.tld/callback
# Subdomain takeover en parent domain
https://app.target.tld.attacker.tld/callback
# Path traversal
https://app.target.tld/callback/../redirect/external?url=attacker.tld
# Open redirect en endpoint del client
https://app.target.tld/redirect?url=attacker.tld (si /redirect existe y redirige)
# Backslash normalization (ya documentado en otro artículo)
https://app.target.tld/\attacker.tld
# URL parsing differential
https://app.target.tld@attacker.tld/callback
https://app.target.tld#@attacker.tld/callback
https://app.target.tld%23.attacker.tld/callback
Cuando el authorization server hace validación parcial
Si el AS valida solo el dominio (no el path completo) y el cliente tiene un endpoint que refleja parte del query string, se puede inyectar HTML/JS que filtre el code via Referer o window.location.
3. Response type confusion
OAuth soporta varios response_type:
code— Authorization Code flow (standard).token— Implicit flow (deprecated, devuelve access_token directamente en URL fragment).code id_token— OpenID Connect hybrid.none,id_token, etc.
El bug
Algunos clientes registran solo el flow code pero el AS acepta downgrade a token:
https://as.target.tld/authorize?
client_id=app
&redirect_uri=https://attacker.tld/cb (si redirect_uri es laxo)
&response_type=token (downgrade a implicit)
&scope=read
El AS responde con https://attacker.tld/cb#access_token=...&token_type=bearer. El access_token va en URL fragment → Referer y proxies pueden capturar.
4. Code/Token leakage
Via Referer header
<!-- Página atacante en https://attacker.tld/leak -->
<img src="https://target.tld/?key=..." />
Si la página post-OAuth callback contiene recursos cargados de dominios externos (analytics, ads, fonts, images), el Referer header lleva la URL completa con el code query param.
Mitigación: callback page con <meta name="referrer" content="no-referrer"> o Referrer-Policy: strict-origin.
Via window.opener
Si el flow OAuth abre popup y el popup carga el callback con code en URL:
// Página atacante (popup opener)
window.open("https://target.tld/oauth/start", "popup");
setInterval(() => {
try {
console.log(popup.location.href); // ¿accesible cross-origin?
} catch {}
}, 100);
Si la callback page no usa Cross-Origin-Opener-Policy: same-origin, el opener puede leer parte de la URL del popup.
Via postMessage
Si el callback hace window.opener.postMessage(window.location.search, '*') con targetOrigin *, cualquier opener captura el code.
5. Account linking takeover
Cuando un usuario linka una cuenta OAuth (Google, GitHub, Facebook) a su cuenta del target:
- Atacante crea cuenta en el target con email
victima@email.tld. - Atacante inicia flow "link Google account" desde su sesión.
- Captura el flow.
- Hace que víctima visite el callback con el
codecontrolado por el atacante. - La cuenta del atacante en Google ahora se vincula a la cuenta del target controlada por el atacante.
- Si el target permite login solo con Google, el atacante puede ahora loguearse usando su Google → entra a la cuenta target con email
victima@email.tld.
Mitigación: verificar email match entre OAuth provider y cuenta target antes de linkar.
6. Pre-account takeover via OAuth
Atacante:
- Registra cuenta en el target con
victima@email.tldsin verificar email. - Víctima más tarde se registra en el target via "Sign in with Google" usando el mismo email.
- Si el target hace merge automático sin verificar que el atacante había confirmado el email, ambos comparten la cuenta — atacante ya tiene credenciales internas + víctima cree que es suya.
Patrón: scope inflation
Cliente pide scope=read profile originalmente. Si en el flow puedes inyectar &scope=read profile admin y el AS no valida estrictamente:
- El AS pide consentimiento al user con scope ampliado.
- Si user acepta sin leer (común), tokens emitidos tienen el scope ampliado.
Verifica que el AS rechace scopes no registrados para el client_id.
Hunting checklist
- ¿Hay
stateparameter? ¿Se valida server-side? ¿Es predecible? - ¿
redirect_urise valida con regex laxo o startsWith? Probar bypasses. - ¿El cliente expone redirect endpoints con
?url=que sirvan como pivot? - ¿
response_type=tokenestá habilitado pero no se usa normalmente? - ¿Callback page tiene
Referrer-PolicyyCOOPheaders? - ¿postMessage con
*en callback? - ¿Account linking valida email match?
- ¿
scopese valida contra el client_id registrado? - ¿Hay endpoints
/oauth/init,/oauth/start,/oauth/connectcon flujos diferentes? Cada uno potencialmente con su propia vuln.
Labs relacionados
Practica state CSRF, redirect_uri bypass y account linking takeover en flows OAuth reales: labs de OAuth.
Practica esto en un lab
Oauth
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Artículos relacionados
CSRF (Cross-Site Request Forgery) — explicado completo con bypasses
CSRF: cómo se explota, defensas comunes (tokens, SameSite, Origin), bypasses (method change, JSON, double-submit, content-type) y dónde buscarlo en cualquier app.
OAuth attacks — state CSRF, redirect_uri bypass, token reuse, IDOR de tokens
Vulnerabilidades en flujos OAuth 2.0: ausencia de state parameter, redirect_uri loose validation, token reuse cross-app, IDOR en endpoints de token refresh.
Cookie flags — Secure, HttpOnly, SameSite y por qué importan
HttpOnly bloquea XSS-to-cookie, Secure obliga HTTPS, SameSite mata CSRF. Cómo se rompen y qué reportar cuando faltan en cookies sensibles.