Nuevos labs cada semana — Accede a todos desde 5€/mes

Nivel IntermedioCon cuenta

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.

Gorka El Bochi9 de mayo de 202614 min

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)

ini
[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 code en 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

bash
# 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:

ini
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

html
<!-- 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:

javascript
// 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:

  1. Atacante crea cuenta en el target con email victima@email.tld.
  2. Atacante inicia flow "link Google account" desde su sesión.
  3. Captura el flow.
  4. Hace que víctima visite el callback con el code controlado por el atacante.
  5. La cuenta del atacante en Google ahora se vincula a la cuenta del target controlada por el atacante.
  6. 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:

  1. Registra cuenta en el target con victima@email.tld sin verificar email.
  2. Víctima más tarde se registra en el target via "Sign in with Google" usando el mismo email.
  3. 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 state parameter? ¿Se valida server-side? ¿Es predecible?
  • ¿redirect_uri se valida con regex laxo o startsWith? Probar bypasses.
  • ¿El cliente expone redirect endpoints con ?url= que sirvan como pivot?
  • ¿response_type=token está habilitado pero no se usa normalmente?
  • ¿Callback page tiene Referrer-Policy y COOP headers?
  • ¿postMessage con * en callback?
  • ¿Account linking valida email match?
  • ¿scope se valida contra el client_id registrado?
  • ¿Hay endpoints /oauth/init, /oauth/start, /oauth/connect con 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

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta

Artículos relacionados