Las vulnerabilidades de OAuth 2.0 encabezan sistemáticamente las listas de recompensas de severidad crítica en programas de bug bounty. Una sola toma de control de cuenta causada por una configuración incorrecta de OAuth puede derivar en el compromiso total de una organización, y estas vulnerabilidades aparecen en aplicaciones utilizadas por miles de millones de personas.
Comprender qué flujo utiliza un objetivo determina qué ataques deben probarse. El flujo de código de autorización (con PKCE) es el más seguro y habitual. El ahora obsoleto flujo implícito presenta numerosas vulnerabilidades. Las credenciales de cliente (de máquina a máquina) tienen su propia superficie de ataque. Nunca debe usarse en producción: credenciales de contraseña del propietario del recurso.
Si el servidor de autorización valida redirect_uri con una precisión insuficiente, un atacante puede redirigir el código de autorización a un servidor bajo su control.
# Solicitud legítima
https://auth.example.com/oauth/authorize?
client_id=app123&
redirect_uri=https://app.com/callback&
response_type=code&state=xyz
# Intentos de ataque
# 1. Recorrido de rutas
redirect_uri=https://app.com/callback/../../../evil.com/steal
# 2. Ruta adicional
redirect_uri=https://app.com.evil.com/callback
# 3. Evasión mediante fragmento
redirect_uri=https://app.com/callback%23@evil.com
# 4. Evasión mediante cadena de consulta
redirect_uri=https://app.com/callback?next=https://evil.com
El parámetro state evita ataques CSRF durante los flujos de OAuth. Si está ausente o es predecible, un atacante puede obligar a las víctimas a conectar sus cuentas con identidades controladas por él, lo que permite tomar el control de las cuentas.
# Compruebe si state:
# 1. Está presente en la solicitud de autorización
# 2. Se valida en la devolución de llamada
# 3. Es criptográficamente aleatorio (no secuencial ni predecible)
# 4. Es de un solo uso (los valores state reutilizados deben rechazarse)
# Prueba: reutilizar el mismo parámetro state
curl -s "https://app.com/oauth/callback?code=VALID_CODE&state=PREVIOUSLY_USED_STATE"
# Debe devolver: error (state ya utilizado)
# Los códigos de autorización deben ser de un solo uso
# Pruébelo reutilizando el código después del primer intercambio:
# Primer uso (legítimo)
POST /oauth/token
code=AUTH_CODE_123&grant_type=authorization_code&...
# Segundo uso (debe fallar)
POST /oauth/token
code=AUTH_CODE_123&grant_type=authorization_code&...
# Resultado esperado: {"error": "invalid_grant"}
PKCE evita los ataques de interceptación del código de autorización. Si un servidor compatible con PKCE no exige su uso, los clientes públicos son vulnerables.
# Iniciar el flujo CON PKCE (legítimo)
code_verifier = generate_random_string(64)
code_challenge = base64url(sha256(code_verifier))
GET /authorize?
code_challenge=abc123&
code_challenge_method=S256&...
# Ataque: intercambiar el código SIN proporcionar code_verifier
POST /token
grant_type=authorization_code&
code=INTERCEPTED_CODE&
redirect_uri=https://legitimate-app.com/callback
# Falta: code_verifier
# Si funciona -> PKCE no se aplica obligatoriamente -> Vulnerabilidad crítica
# Intentar degradar de S256 a plain (menos seguro)
GET /authorize?
code_challenge=ACTUAL_CODE_VERIFIER&
code_challenge_method=plain&...
# Si se acepta: el servidor podría estar usando plain en lugar de S256
# Después, intercambiar con code_verifier = texto sin formato original
POST /token
code_verifier=ORIGINAL_PLAIN_TEXT&...
En el flujo implícito (obsoleto, pero aún presente), los tokens de acceso incluidos en fragmentos de URL (#access_token=...) no se envían en las cabeceras Referer, pero los tokens incluidos en parámetros de consulta (?access_token=...) sí. Compruebe siempre cómo aparecen los tokens en las URL.
# Solicitar un alcance mínimo
GET /authorize?scope=read:email&...
# Después de obtener el token de acceso, probar llamadas no autorizadas a la API
GET /api/user/delete
Authorization: Bearer ACCESS_TOKEN_WITH_READ_SCOPE
# Debe devolver 403, no 200
# Si los tokens de acceso son JWT, pruebe:
# 1. Confusión de algoritmos (RS256 -> HS256)
header = {"alg": "HS256", "typ": "JWT"}
# Firmar usando la clave pública como secreto HMAC
# 2. Algoritmo none
header = {"alg": "none", "typ": "JWT"}
# Algunas bibliotecas aceptan tokens sin firmar
# 3. Inyección en kid (si se utiliza 'kid')
header = {"alg": "HS256", "kid": "' OR 1=1--"}
# Inyección SQL en el parámetro kid
# Herramientas:
# jwt_tool: python3 jwt_tool.py TOKEN -T (manipular)
# jwt-cracker: para secretos débiles
# Probar si los tokens de actualización antiguos se invalidan después de la rotación
POST /token
grant_type=refresh_token&refresh_token=OLD_REFRESH_TOKEN
# Si devuelve un token nuevo -> el token antiguo no se invalidó
# Un atacante que haya robado el token de actualización antiguo aún puede obtener nuevos tokens de acceso
Si una víctima se registra con correo electrónico y contraseña (correo: victim@gmail.com) y un atacante puede vincular una cuenta de Google mediante OAuth con el mismo correo para tomar el control de la cuenta de la víctima, sin que Google verifique la propiedad, se trata de una toma de control de cuenta crítica. Esto es especialmente habitual en plataformas que permiten vincular varios proveedores de autenticación.
# Escenario de ataque:
# 1. El atacante crea una cuenta con victim@example.com (antes de que se registre la víctima)
# 2. El atacante vincula su proveedor de OAuth a este correo
# 3. Cuando la víctima se registra posteriormente mediante Google OAuth usando victim@example.com
# 4. La víctima obtiene acceso a la cuenta creada previamente por el atacante (o viceversa)
# Para probarlo:
# 1. Registrar una cuenta con el correo de la víctima (antes de que exista)
# 2. Conectar OAuth desde otro correo
# 3. Intentar acceder a la futura cuenta de la víctima
# Algunas implementaciones permiten especificar access_token en la solicitud
# o no vinculan correctamente los tokens a las sesiones
# Prueba: forzar un valor de token conocido
GET /oauth/callback?access_token=KNOWN_VALUE
# Si la aplicación acepta y utiliza este token -> fijación de tokens
# Si una aplicación vincula las cuentas al correo electrónico (y no a sub):
# Registrar un proveedor de OAuth malicioso con el correo de la víctima como declaración 'email'
# Algunos proveedores permiten establecer declaraciones de correo arbitrarias
# Probar creando un proveedor de OAuth personalizado con:
{
"sub": "attacker-unique-id",
"email": "victim@example.com",
"email_verified": true
}
# Los clientes confidenciales deben autenticarse mediante client_secret
# Probar si client_secret es opcional:
POST /token
grant_type=authorization_code&
code=CODE&
client_id=CONFIDENTIAL_CLIENT_ID&
# Falta: client_secret
# Si funciona -> la autenticación del cliente no se exige
# El flujo de dispositivos (para dispositivos con entrada limitada) puede utilizarse para phishing
POST /device/code
client_id=LEGITIMATE_CLIENT
# Devuelve un user_code que la víctima introduce en verification_uri
# El atacante lo utiliza para ingeniería social:
# "Introduzca el código XXXX-XXXX en accounts.target.com/activate"
| Tipo de vulnerabilidad | Plataforma | Impacto | Recompensa |
|---|---|---|---|
| Evasión de redirect_uri | Toma de control de cuenta | 5,000 | |
| Ausencia del parámetro state | Airbnb | CSRF → ATO | $8,000 |
| Reutilización del código | Microsoft | Ataque de repetición | 5,000 |
| Algoritmo none de JWT | Auth0 | Evasión de la autenticación | 0,000 |
| ATO por confusión de correo electrónico | Slack | Toma de control de cuenta | 0,000 |
| PKCE no aplicado obligatoriamente | Varias | Interceptación del código | $5,000+ |
El escáner de KENSAI basado en IA prueba automáticamente sus implementaciones de OAuth para detectar interceptación del código de autorización, evasión de PKCE, vulnerabilidades de tokens y escenarios de toma de control de cuentas.
Iniciar análisis gratuito →La seguridad no es opcional.
🗡️ El equipo de KENSAI