Confusión en algoritmos JWT: convertir tokens RS256 en desastres HS256 🔄

Entendiendo la vulnerabilidad crítica de seguridad que convierte claves públicas en secretos
En el panorama de la seguridad en aplicaciones web modernas, los JSON Web Tokens (JWTs) se han vuelto omnipresentes para gestionar autenticación y autorización. Sin embargo, una vulnerabilidad sutil pero devastadora acecha en muchas implementaciones de JWT: los ataques de confusión de algoritmos. Este vector de ataque explota la forma en que los servidores validan las firmas JWT, permitiendo a los atacantes falsificar tokens manipulando el algoritmo especificado en el encabezado del token.
¿Qué es la confusión en algoritmos JWT?
Las vulnerabilidades por confusión de algoritmos generalmente surgen debido a implementaciones defectuosas de bibliotecas JWT, donde muchas proporcionan un método único, independiente del algoritmo, para verificar firmas. La vulnerabilidad ocurre cuando un servidor no aplica correctamente qué algoritmo criptográfico debe usarse para verificar la firma de un JWT.
La confusión en algoritmos sucede cuando un sistema no verifica adecuadamente el tipo de firma utilizada en un JWT, permitiendo que un atacante explote la falta de distinción entre diferentes métodos de firma. La variante más peligrosa implica cambiar de RS256 (RSA con SHA-256, un algoritmo asimétrico) a HS256 (HMAC con SHA-256, un algoritmo simétrico).
La diferencia fundamental entre RS256 y HS256
Comprender la confusión en algoritmos requiere entender la diferencia básica entre criptografía simétrica y asimétrica:
RS256 (RSA + SHA-256) - Asimétrico: - Usa una llave privada para firmar tokens - Usa una llave pública matemáticamente relacionada para verificar firmas - La llave privada debe mantenerse en secreto - La llave pública puede compartirse abiertamente - Dos llaves diferentes para dos propósitos distintos
HS256 (HMAC + SHA-256) - Simétrico: - Usa una única llave secreta para firmar y verificar - La misma llave realiza ambas operaciones - La llave secreta debe mantenerse confidencial - Cualquier que tenga la llave puede crear y verificar tokens
Cómo funciona el ataque: el cambio mortal
La variante más común implica intercambiar un token RS256 por uno HS256, y luego usar la llave pública RSA como secreto HMAC. Aquí el flujo del ataque:
Paso 1: Obtener un JWT válido
El atacante obtiene primero un JWT legítimo de la aplicación, típicamente firmado con RS256:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwicm9sZSI6InVzZXIifQ.signature
Este token tiene tres partes:
- Header: {"alg":"RS256","typ":"JWT"}
- Payload: {"sub":"user123","role":"user"}
- Signature: Verificada usando la llave pública RSA del servidor
Paso 2: Obtener la llave pública
Los servidores a veces exponen sus llaves públicas como objetos JSON Web Key (JWK) a través de un endpoint estándar mapeado a /jwks.json o /.well-known/jwks.json. Los atacantes pueden obtener las llaves públicas mediante varios métodos:
- Endpoints JWKS estándar (
/.well-known/jwks.json) - Documentación o archivos de configuración del servidor
- Certificados SSL/TLS
- Derivando llaves de múltiples JWT capturados usando herramientas como jwt_forgery.py
Paso 3: Modificar el encabezado y payload del token
El atacante cambia el algoritmo en el encabezado de RS256 a HS256 y modifica el payload para escalar privilegios:
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "user123",
"role": "admin"
}
Paso 4: Firmar usando la llave pública como secreto HMAC
Falsificar tokens implica firmar el payload del token usando la llave pública en formato PEM como clave HMAC. El atacante firma el token modificado usando HS256, tratando la llave pública RSA del servidor como el secreto HMAC.
Paso 5: El servidor acepta el token falsificado
Cuando el servidor recibe este token malicioso, la vulnerabilidad se manifiesta en cómo el método de verificación lo procesa:
Los problemas surgen cuando los desarrolladores asumen que el método de verificación solo manejará JWT firmados con un algoritmo asimétrico como RS256, y siempre pasan una llave pública fija al método.
// Patrón de código vulnerable
publicKey = public-key-of-server;
token = request.getCookie("session");
verify(token, publicKey);
Si el servidor recibe un token firmado con un algoritmo simétrico como HS256, la función de verificación genérica tratará la llave pública como un secreto HMAC. El servidor, sin saberlo, usa su propia llave pública como secreto HMAC para verificar la firma del atacante, lo cual valida con éxito porque el atacante usó la misma llave pública para crear la firma.
Impacto en el mundo real: vulnerabilidades recientes
La confusión en algoritmos no es solo una amenaza teórica. Los descubrimientos recientes demuestran su prevalencia continua:
CVE-2024-54150 (diciembre 2024)
Se descubrió una vulnerabilidad en la biblioteca cjwt donde la función cjwt_decode no requiere que los desarrolladores elijan un algoritmo, y el código para manejar claves HMAC y RSA era idéntico. La estructura switch de la biblioteca procesaba tokens HS256 usando la clave proporcionada sin detectar que se pasó una clave pública como secreto.
CVE-2024-37568 (2024)
Esta vulnerabilidad crítica en Authlib, una popular biblioteca Python para OAuth y OpenID Connect, surgió cuando la reclamación ‘alg’ faltaba o era incorrecta, causando que Authlib predeterminara incorrectamente la verificación HMAC incluso cuando había una clave pública presente.
CVE-2023-48238 (2023)
Se encontró vulnerable la biblioteca json-web-token porque el algoritmo para verificar la firma se tomaba del propio token JWT, que en ese momento aún no estaba verificado y no debería confiarse.
Por qué existe esta vulnerabilidad: las causas raíz
Diseño defectuoso de la biblioteca
El encabezado JWT contiene un parámetro alg que indica al servidor qué algoritmo se usó para firmar el token y qué algoritmo debe usar al verificar la firma, creando un diseño inherentemente defectuoso porque el servidor no tiene más opción que confiar implícitamente en la entrada controlada por el usuario del token, que no ha sido verificada.
Malentendido del desarrollador
Muchos desarrolladores malinterpretan las implicaciones de seguridad de los métodos de verificación genéricos. Asumen que pasar una llave pública significa automáticamente que la biblioteca solo aceptará firmas asimétricas, sin darse cuenta de que la biblioteca confía en el algoritmo especificado en el encabezado no verificado.
Validación insuficiente
Las aplicaciones a menudo no validan que el algoritmo en el encabezado del token coincida con su algoritmo esperado. Sin esta comprobación, los atacantes pueden sustituir algoritmos a voluntad.
Demostración del ataque: explotación práctica
Así es como un atacante podría ejecutar este ataque usando herramientas comunes:
Usando jwt_tool
# Extraer la llave pública del endpoint JWKS
curl https://target.com/.well-known/jwks.json jwks.json
# Crear un token falsificado
python jwt_tool.py original_token.txt -X k -pk public_key.pem
Implementación manual en Python
import jwt
import base64
# Leer la llave pública
with open('public_key.pem', 'r') as f:
public_key = f.read()
# Crear payload malicioso
payload = {
'sub': 'user123',
'role': 'admin',
'iat': 1234567890
}
# Firmar usando HS256 con la llave pública como secreto
forged_token = jwt.encode(
payload,
public_key,
algorithm='HS256'
)
print(forged_token)
Usando Burp Suite
Una vez que tienes la llave pública en un formato adecuado, puedes modificar el JWT como desees, asegurando que el encabezado alg esté en HS256, y luego firmar el token usando HS256 con la llave pública RSA como secreto.
Técnicas avanzadas de explotación
Recuperación de llave pública
En casos donde la llave pública no está fácilmente disponible, aún puedes probar la confusión en algoritmos derivando la llave de un par de JWTs existentes usando herramientas como jwt_forgery.py.
La relación matemática entre múltiples firmas creadas con la misma llave privada puede ser explotada para reconstruir la llave pública, requiriendo solo dos JWTs firmados con la misma clave.
Sensibilidad al formato
La llave pública que uses para firmar el token debe ser exactamente igual a la almacenada en el servidor, incluyendo el mismo formato y preservando caracteres no imprimibles como saltos de línea. Los atacantes a menudo necesitan experimentar con diferentes formatos de llave:
- Formato PEM con encabezados
- Material de llave en bruto
- Diferentes estilos de salto de línea (CRLF vs LF)
- Con o sin saltos de línea finales
Detección y pruebas
Identificación de aplicaciones vulnerables
Los investigadores de seguridad pueden probar la confusión en algoritmos:
- Analizando los encabezados JWT para identificar el algoritmo de firma en uso
- Localizando las llaves públicas mediante endpoints JWKS u otros medios
- Intentando el ataque modificando el algoritmo y firmando con la llave pública
- Observando el comportamiento del servidor para ver si acepta el token falsificado
Escaneo automatizado
Desde Burp Suite Professional 2022.5.1, Burp Scanner puede detectar automáticamente varias vulnerabilidades en mecanismos JWT, incluyendo ataques de confusión en algoritmos.
Estrategias de defensa: protegiendo tus aplicaciones
1. Especificar explícitamente los algoritmos esperados
Las bibliotecas JWT deben agregar un parámetro de algoritmo a su función de verificación, y el servidor ya debería conocer qué algoritmo usa para firmar tokens.
Implementación segura:
// Node.js con jsonwebtoken
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
# Python con PyJWT
jwt.decode(token, public_key, algorithms=['RS256'])
// Java con jjwt
Jwts.parserBuilder()
.setSigningKey(publicKey)
.requireAlgorithm(SignatureAlgorithm.RS256)
.build()
.parseClaimsJws(token);
2. Uso de listas blancas de algoritmos
Es preferible adoptar una lista blanca, definiendo explícitamente los algoritmos autorizados como HS256 o RS256, lo que garantiza un control estricto y evita brechas causadas por interpretaciones laxas del algoritmo.
3. Separar la lógica de verificación por tipo de llave
Nunca usar el mismo método de verificación para claves simétricas y asimétricas. Implementar rutas de código separadas:
function verifyToken(token, expectedAlgorithm, key) {
// Extraer algoritmo del encabezado solo para registro
const header = JSON.parse(
Buffer.from(token.split('.')[0], 'base64').toString()
);
// NUNCA confiar en el algoritmo del encabezado
// Siempre usar el algoritmo esperado
if (expectedAlgorithm === 'RS256') {
return verifyRS256(token, key);
} else if (expectedAlgorithm === 'HS256') {
return verifyHS256(token, key);
}
throw new Error('Algoritmo no soportado');
}
4. Implementar comprobaciones de tipo
Algunas bibliotecas ahora incluyen protecciones que detectan cuando se usa una llave pública donde se espera un secreto simétrico:
function isPublicKey(key) {
return key.includes('BEGIN PUBLIC KEY') ||
key.includes('BEGIN RSA PUBLIC KEY');
}
function verifyHS256(token, secret) {
if (isPublicKey(secret)) {
throw new Error('La llave pública no puede usarse como secreto HMAC');
}
return jwt.verify(token, secret, { algorithms: ['HS256'] });
}
5. Rechazar el algoritmo “none”
El algoritmo none está pensado para usarse en situaciones donde la integridad del token ya ha sido verificada, pero desafortunadamente algunas bibliotecas tratan los tokens firmados con el algoritmo none como válidos con firma verificada.
Siempre rechazar explícitamente tokens que usen el algoritmo “none” en producción:
const decoded = jwt.verify(token, key, {
algorithms: ['RS256', 'HS256'],
// El algoritmo 'none' se rechaza automáticamente
});
6. Usar secretos fuertes para HMAC
Al implementar JWT, los desarrolladores a veces cometen errores como olvidar cambiar secretos predeterminados o de marcador de posición, haciendo trivial que un atacante fuerza la contraseña del servidor usando una lista de secretos conocidos.
Para implementaciones HS256: - Usar secretos aleatorios criptográficamente seguros de al menos 256 bits - Almacenar secretos de forma segura en variables de entorno o sistemas de gestión de secretos - Rotar secretos regularmente - Nunca codificar secretos en el código fuente
7. Validar todos los claims minuciosamente
Más allá de la validación del algoritmo, implementar validaciones exhaustivas de claims:
jwt.verify(token, publicKey, {
algorithms: ['RS256'],
issuer: 'https://trusted-issuer.com',
audience: 'tu-aplicacion',
clockTolerance: 60 // segundos de margen para comparaciones de tiempo
});
8. Mantener actualizadas las bibliotecas
Para abordar CVE-2024-37568, es imprescindible actualizar Authlib a la versión 1.3.1 o superior, que incorpora correcciones robustas para prevenir confusión en algoritmos y garantizar una validación JWT correcta.
Actualizar regularmente las bibliotecas JWT para beneficiarse de parches de seguridad y mejoras en la lógica de validación.
Mejores prácticas para la seguridad JWT
Lista de verificación de seguridad integral
Gestión de algoritmos: - ✅ Especificar explícitamente los algoritmos permitidos en las llamadas de verificación - ✅ Nunca confiar en el algoritmo del encabezado del token - ✅ Desactivar el algoritmo “none” en producción - ✅ Usar algoritmos asimétricos (RS256, ES256) en la mayoría de los casos - ✅ Implementar listas blancas de algoritmos a nivel de aplicación
Gestión de claves: - ✅ Almacenar las claves privadas de forma segura usando servicios de gestión de claves - ✅ Usar secretos fuertes y aleatorios para HMAC - ✅ Rotar claves regularmente con períodos de gracia para la transición - ✅ Mantener las claves públicas accesibles pero proteger las privadas - ✅ Nunca incrustar claves en código fuente o archivos de configuración
Manejo de tokens: - ✅ Establecer tiempos de expiración cortos (15 minutos o menos para tokens de acceso) - ✅ Implementar rotación de tokens de actualización - ✅ Validar todos los claims estándar (iss, aud, exp, nbf, iat) - ✅ Usar exclusivamente HTTPS para transmisión de tokens - ✅ Almacenar tokens de forma segura (cookies HttpOnly para aplicaciones web)
Implementación: - ✅ Usar bibliotecas JWT bien mantenidas y actualizadas - ✅ Implementar manejo de errores adecuado sin filtrar información - ✅ Registrar eventos de seguridad para monitoreo y respuesta a incidentes - ✅ Realizar auditorías de seguridad y pruebas de penetración periódicas - ✅ Educar a los desarrolladores en principios de seguridad JWT
Selección del algoritmo correcto
Para nuevas aplicaciones: - RS256 o ES256: Mejor para la mayoría de escenarios que requieren distribución de clave pública - PS256: Seguridad mejorada sobre RS256 con firmas probabilísticas - EdDSA: Más seguro y eficiente, excelente para nuevas implementaciones
Evitar: - HS256 para APIs públicas donde la clave secreta podría exponerse - Claves débiles: mínimo 2048 bits para RSA, 256 bits para ECDSA - Flexibilidad de algoritmos: no soportar múltiples algoritmos a menos que sea absolutamente necesario
Pruebas de tu implementación
Pasos para evaluación de vulnerabilidades
- Revisar el código de verificación para asegurar que los algoritmos estén explícitamente especificados
- Probar con tokens modificados donde se cambie el algoritmo
- Intentar sustitución de clave pública para verificar las protecciones
- Verificar aceptación del algoritmo “none” en todos los entornos
- Comprobar sensibilidad al formato de clave para garantizar seguridad
Herramientas de prueba de seguridad
- jwt_tool: kit completo para pruebas JWT
- Burp Suite: pruebas de seguridad en aplicaciones web con extensiones JWT
- OWASP ZAP: escáner de seguridad de código abierto con soporte JWT
- Scripts personalizados: scripts en Python o Node.js para casos específicos
Panorama general: el escenario de seguridad JWT
La confusión en algoritmos es solo una de varias vulnerabilidades JWT que las aplicaciones deben defender:
- Claves débiles: fuerza bruta de secretos HMAC
- Falta de verificación de firma: aceptar tokens sin firma
- Filtración de tokens: exposición mediante logs, URLs o almacenamiento en cliente
- Repetición de ataques: reutilización de tokens capturados
- Inyección JKU/X5U: manipulación de parámetros en encabezado
- Inyección Kid: manipulación de identificador de clave
Los JWT no son seguros solo por ser JWT; depende de cómo se usen.
Conclusión: la vigilancia es esencial
La confusión en algoritmos JWT representa una vulnerabilidad de seguridad crítica que ha afectado a bibliotecas importantes y sigue surgiendo en nuevas implementaciones. El ataque explota una confianza fundamental: permitir que tokens no verificados dicten cómo deben verificarse.
Nunca confiar en el campo “alg” del propio JWT, y hacer cumplir el algoritmo esperado a nivel de configuración. Implementando validación explícita del algoritmo, usando métodos de verificación específicos y siguiendo las mejores prácticas de seguridad, los desarrolladores pueden proteger sus aplicaciones de este vector de ataque devastador.
La clave: Nunca confiar en la entrada del usuario, especialmente cuando determina operaciones críticas de seguridad. El campo del algoritmo en el encabezado JWT es datos controlados por el usuario desde una fuente no verificada. Trátalo con el mismo escepticismo que cualquier otra entrada no confiable, y siempre aplica tu política de seguridad de forma explícita en el código.
A medida que los mecanismos de autenticación evolucionan, mantenerse informado sobre vulnerabilidades como la confusión en algoritmos y aplicar estrategias de defensa en profundidad es vital para mantener la seguridad de la aplicación. Auditorías de seguridad regulares, educación de desarrolladores y monitoreo proactivo son los pilares de una postura de seguridad JWT robusta.
Recursos para aprender más:
- Guía de seguridad JWT de OWASP
- RFC 7519: JSON Web Token (JWT)
- RFC 8725: Mejores prácticas actuales de JWT
- PortSwigger Web Security Academy: Ataques JWT
- Herramientas de prueba JWT de Burp Suite
¡Mantente seguro, verifica explícitamente y nunca confíes en el encabezado del algoritmo! 🔒
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.