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.
La checklist que repaso en cada objetivo nuevo
47 comprobaciones ordenadas por coste: primero lo que te puede meter en un lío, luego lo barato, y al final lo caro — que es donde están los bounties grandes. Te la mando al correo ahora mismo.
Te das de baja en un clic, desde cualquier correo.
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.
