Aprende › Vulnerabilidades web

OAuth 2.0: los fallos clásicos de redirección, state, scope y PKCE

· 10 min de lectura

OAuth 2.0 delega autenticación a terceros. Mal implementado abre fallos en redirect_uri, state (CSRF), scope elevation, PKCE ausente, implicit flow y token leakage. Aprende cada vector con ejemplos y las correcciones.

Cuando haces clic en «Iniciar sesión con Google», «Continuar con GitHub» o «Conectar con Microsoft», detrás hay un protocolo llamado OAuth 2.0 que delega la autenticación a un proveedor externo y la aplicación recibe un token para operar en tu nombre con los permisos que autorizaste. OAuth es la base de la autenticación federada en la web moderna, y por eso es también una de las superficies más atacadas: una sola implementación mal hecha abre acceso a cuentas, filtra tokens o permite impersonación total.

Esta lección cubre los fallos más comunes que siguen apareciendo en bug bounty programs y en auditorías de aplicaciones en 2026: redirect_uri laxo, state missing (CSRF), scope elevation, ausencia de PKCE, implicit flow obsoleto, mix-up attacks, token leakage por referer y assertion tokens sin audience. Es la tercera y última lección del subcluster API de la Tanda 4, cerrando el bloque junto a BOLA y Mass Assignment / GraphQL.

Prueba solo contra aplicaciones autorizadas o laboratorios. Robar tokens OAuth en producción ajena es intrusión y delito.

OAuth 2.0 en una imagen#

OAuth Authorization Code: usuario → cliente → authorization server → cliente recibe code → cliente cambia code por token → usa token para API
OAuth Authorization Code: usuario → cliente → authorization server → cliente recibe code → cliente cambia code por token → usa token para API

Flujo canónico (Authorization Code with PKCE, el recomendado):

  1. El usuario quiere autenticarse en una app (client).
  2. El client lo redirige al authorization server (Google, GitHub, Okta).
  3. El usuario se autentica y autoriza los scopes.
  4. El authorization server redirige al client con un code (en la URL).
  5. El client intercambia el code por un access_token vía HTTPS back-channel.
  6. El client usa el access_token contra el resource server (APIs del proveedor).

Cada paso tiene su defensa; cada defensa saltada es un fallo.

redirect_uri laxo#

El redirect_uri es la URL a la que el authorization server devuelve el code. Si la validación es laxa (startsWith, wildcard, subdominios), un atacante registra una URL controlada y robas el code:

# Legítimo
redirect_uri=https://app.com/callback

# Ataque si la validación es startsWith
redirect_uri=https://app.com.attacker.com/callback

# Si acepta subdominios con wildcard
redirect_uri=https://userupload.app.com/callback
# El atacante subió un archivo HTML malicioso ahí

# Path traversal
redirect_uri=https://app.com/callback/../attacker/

Code llega al atacante, intercambia por token, controla la cuenta. La defensa correcta es exact match de redirect_uri contra una whitelist pre-registrada.

State missing: CSRF#

El parámetro state protege contra CSRF en el flow: el client genera un nonce, lo guarda en session, lo pasa al authorization server. Al volver, verifica que coincide. Sin state, un atacante:

  • Inicia el flow OAuth con su propia cuenta.
  • Intercepta el code y lo pega en un enlace que envía a la víctima.
  • La víctima hace click, el client acepta el code sin verificar state.
  • La cuenta de la víctima queda vinculada a la cuenta del atacante en el proveedor.

Ahora cuando la víctima hace «login with Google», entra a la cuenta del atacante. Account takeover silencioso. La defensa es generar state aleatorio, guardarlo en session, verificarlo al callback.

Scope elevation#

El client pide scopes mínimos al principio, pero al renovar o en segunda autorización pide más. Si el authorization server no valida bien, el atacante obtiene scopes que el usuario no quiso dar.

Variante más seria: una aplicación legítima pide scope read:email; una aplicación maliciosa que robó las credenciales pide admin:org al mismo endpoint sin que el usuario vea claramente el cambio.

PKCE ausente#

PKCE (Proof Key for Code Exchange) es un parámetro que previene que un atacante use un code interceptado. El client genera un code_verifier aleatorio, lo hashea como code_challenge, lo pasa en la petición. Al intercambiar code por token, envía el code_verifier original; el server verifica que el hash coincide.

Sin PKCE, un atacante que roba un code (por log, por referer, por network) puede usarlo. Con PKCE, el code es inútil sin el verifier que solo el client legítimo conoce.

PKCE es obligatorio en clientes públicos (SPAs, móviles), y recomendado en todos. En 2026 no hay excusa para no usarlo.

Implicit flow obsoleto#

El implicit flow devolvía el token directamente en la URL de redirect en vez de un code. Era la opción para SPAs antes de PKCE. Problema: el token aparece en logs del navegador, en referer headers, y es trivial de robar con XSS.

OAuth 2.1 (draft) elimina implicit flow. Para SPAs se usa Authorization Code + PKCE.

Si tu aplicación aún usa implicit flow: migrar. Si no puedes migrar: al menos vincular el token a origin y usar tokens cortos.

Mix-up attacks#

Mix-up: cliente soporta varios IDPs → atacante empuja al cliente a enviar código a su IDP malicioso → secuestra flujo
Mix-up: cliente soporta varios IDPs → atacante empuja al cliente a enviar código a su IDP malicioso → secuestra flujo

Cliente que soporta múltiples authorization servers (Login with Google OR Login with Microsoft). Atacante manipula el issuer o parámetros del client para que confunda uno con otro:

  • Cliente recibe code de Google pero lo envía a token endpoint de Microsoft (el atacante lo registró).
  • Microsoft responde con token de la cuenta del atacante.
  • Cliente lo asocia al usuario víctima.

Mitigación: iss parameter en responses, verificación estricta del issuer al callback.

Token leakage por referer#

Access tokens en URL (query string) se filtran en:

  • Referer headers cuando la página con el token enlaza a otros sitios.
  • Logs del servidor del destino.
  • History del navegador.
  • Third-party scripts cargados (Google Analytics, intercom).

La regla: tokens en headers HTTP, nunca en URLs. Access tokens NUNCA en query strings. Si llega uno así (por legacy), rotar de inmediato.

Assertion tokens sin audience#

En flujos OIDC (OpenID Connect), el id_token JWT debe incluir claim aud (audience) con el client_id para el que fue emitido. Si el client no verifica aud, un atacante presenta id_token emitido para OTRO cliente (que controla) y suplanta al usuario.

Mitigación trivial pero olvidada: verificar aud, iss, exp, nonce en cada id_token recibido.

Herramientas para testing#

  • OAuth Debugger Burp Extension: visualiza y modifica flows.
  • oauthtester.com: tester público.
  • PKCE verifier/challenge generator.
  • Postman OAuth 2 helper: para pruebas automatizadas.
  • ZAP OAuth2 addon.

Para scanning masivo: configurar Burp con proxy y recorrer el flow completo, modificando cada parámetro para ver respuesta del server.

Checklist de auditoría#

Pentest de una integración OAuth debe verificar:

  • [ ] redirect_uri valida exact match contra whitelist.
  • [ ] state parameter presente, aleatorio, verificado al callback.
  • [ ] PKCE con S256 (no plain).
  • [ ] No usa implicit flow.
  • [ ] Access tokens en headers, nunca en URLs.
  • [ ] id_token verifica aud, iss, exp, nonce, firma.
  • [ ] Scopes mínimos solicitados.
  • [ ] Token lifetime corto, refresh token para renovar.
  • [ ] Logout invalida tokens en server, no solo en cliente.
  • [ ] Rate limiting en token endpoint.

Caso real: Sign in with Apple takeover#

En mayo de 2020, el investigador Bhavuk Jain reportó a Apple un fallo en «Sign in with Apple» que permitía account takeover de cualquier usuario en servicios de terceros que usaran el sistema. La raíz del fallo: Apple firmaba id_tokens para cualquier email proporcionado por el cliente en la petición, sin verificar que el email pertenecía realmente al usuario autenticado. Un atacante solicitaba un token para [email protected] y lo presentaba al servicio tercero, que aceptaba el id_token firmado por Apple y daba acceso a la cuenta de la víctima.

Apple pagó 100 000 USD por el reporte y parcheó en semanas. La lección: la firma del token no es suficiente; el servicio tercero debe verificar también los claims (en este caso, que el email coincidiera con el subject real). Mismo principio en todas las lecciones: nunca confíes en una capa única.

Caso real: GitHub device code phishing#

En 2023 se publicitaron campañas de phishing que abusaban del Device Authorization Grant de OAuth: el atacante inicia un flow de dispositivo (como el que usan smart TVs) y envía la URL con el código a la víctima por email/DM. La víctima, pensando que es una verificación normal, autoriza. Pero el token resultante queda en manos del atacante.

Mitigación: educación de usuarios + GitHub añadió verificación adicional para flows device en organizaciones corporativas.

Errores comunes#

  • Usar wildcards en redirect_uri porque «tenemos muchos entornos».
  • Olvidar PKCE en SPAs por tutoriales viejos.
  • Confiar en que el authorization server valida todo (el client también tiene que validar).
  • No rotar refresh tokens (debe ser one-time-use).
  • Scope admin:* solicitado por aplicaciones que solo necesitan read:email.

Preguntas frecuentes#

¿OAuth 2.1 reemplaza a 2.0?#

OAuth 2.1 es el draft que consolida best practices actuales: PKCE obligatorio, elimina implicit flow, exact match redirect, refresh tokens rotados. En 2026 muchos proveedores siguen soportando 2.0 por compatibilidad.

¿OpenID Connect es OAuth?#

OIDC es una capa de autenticación sobre OAuth 2.0. Añade id_token con identidad del usuario. Hoy la mayoría de login federado usa OIDC, no OAuth plano.

¿Un atacante con client_secret puede hacer todo?#

Sí: ese es el secreto que identifica al client ante el server. En SPAs no hay secret (son públicos); por eso PKCE es obligatorio ahí. En backend clients, el secret se guarda en server, nunca en frontend.

¿Un code OAuth es reutilizable?#

Debería ser one-time-use. Si el server lo acepta dos veces, es fallo de implementación. En PKCE se vincula al verifier, así que doble uso devuelve error distinto a simple reuso.

¿Dónde practico?#

El laboratorio signinbleed de whoami-labs incluye una aplicación con OAuth implementado con múltiples fallos: redirect_uri laxo, state faltante, sin PKCE. Practicas robar codes, forjar account takeover y luego ves la versión corregida.

¿Device Flow debería usarse hoy?#

Solo para dispositivos sin navegador integrado (smart TVs, IoT, CLIs). Añade superficie de phishing (el usuario escribe un código en otro dispositivo) que debe cuidarse: mostrar claramente qué aplicación y qué scopes, bloquear códigos si se originan desde IPs extranjeras, educar al usuario.

¿JWT vs tokens opacos?#

Access tokens pueden ser JWT (self-contained, con firma) o tokens opacos (referencia a estado en el server). JWT escala mejor (no requiere hit al server para validar), pero una revocación es más difícil (hay que esperar a expiración). En aplicaciones críticas, muchos equipos prefieren tokens opacos con introspection endpoint para poder revocar al instante.

¿Qué pasa con MFA en OAuth?#

MFA se aplica en el authorization server (Google, Microsoft), no en el client. El client solo recibe el token; confía en que el authorization server ya verificó MFA si está configurado. Para flujos sensibles (admin, cambio de password), se puede forzar reautenticación reciente con el claim auth_time del id_token.

¿Las aplicaciones móviles tienen requisitos distintos?#

Sí: usan PKCE obligatoriamente, el redirect_uri suele ser un custom URI scheme (myapp://callback) que otras aplicaciones del dispositivo pueden intentar interceptar. Mitigación moderna: Universal Links (iOS) / App Links (Android) que vinculan el redirect a un dominio verificado, impidiendo apps maliciosas se registren con el mismo scheme.

Resumen#

  • OAuth 2.0 delega autenticación a un authorization server; el client recibe un token para operar en nombre del usuario.
  • Fallos clásicos: redirect_uri laxo, state missing (CSRF), scope elevation, sin PKCE, implicit flow, mix-up, token leakage URL, id_token sin aud.
  • Defensa: exact match redirect, state aleatorio, PKCE S256, tokens en headers, verificar aud/iss/exp/nonce.
  • OAuth 2.1 (draft) consolida best practices: PKCE obligatorio, sin implicit.
  • Checklist de auditoría cubre los 10 puntos mínimos.

Fuentes y lecturas recomendadas#

Practícalo en estos laboratorios

Ver todos los laboratorios de esta categoría