Volver al blog
Vulnerabilidad10 min de lecturaActualizado el

JWT alg:none todavía existe en producción en 2026

JWT alg:none fue un meme en 2015. En 2026 aún aparece en producción en SaaS brasileños. Las 4 variantes modernas y cómo probarlas y corregirlas.

Diego Melo, autor
Diego Melo

Investigador de seguridad y fundador de No Vuln

JWT alg:none todavía existe en producción en 2026

En 2015, JWT alg: none se volvió un meme — la vulnerabilidad tan básica que nadie podría seguir teniendo en producción. En 2026, todavía la encuentro en producción. ¿Cómo es posible?

Respuesta corta: el bug original “murió” (las librerías modernas lo rechazan por defecto), pero 4 variantes modernas siguen apareciendo en pentest con una regularidad incómoda. Este post es la guía técnica de cada variante, cómo detectarla en 30 segundos y cómo corregirla.

El bug original (repaso rápido)

JWT permite especificar el algoritmo de firma en el header. En 2015 se descubrió que muchas librerías aceptaban alg: none — es decir, un JWT sin firma. El atacante editaba el payload, quitaba la firma, y el servidor lo aceptaba:

  • Header: {"alg":"none","typ":"JWT"}
  • Payload: {"sub":"admin","role":"admin"}
  • Signature: (vacía)

El servidor lo procesaba como un token válido de admin. Catástrofe global.

Hoy, las librerías modernas (jsonwebtoken v9+, PyJWT 2+, JJWT 0.12+) rechazan alg: none por defecto. Bug muerto. Salvo que no está muerto. Cambió de forma.

Variante 1: Algorithm Confusion (HS256 vs RS256)

El bug: el servidor está configurado para validar el JWT con RS256 (firma asimétrica), pero la función de verificación acepta HS256 (firma simétrica). El atacante usa la clave PÚBLICA RSA del servidor como secreto HMAC.

Por qué funciona:

  • RS256 valida con clave_publica
  • HS256 valida con secreto_compartido
  • La clave pública RSA está disponible en un endpoint público (/.well-known/jwks.json o similar)
  • La función jwt.verify(token, clave_publica) sin especificar el algoritmo acepta ambos
  • El atacante forja un JWT con alg: HS256 usando la clave pública como secreto HMAC
  • El servidor valida con HS256 + clave pública = éxito

Cómo detectarlo en pentest:

  1. Capturar un JWT legítimo
  2. Decodificar el header (sin validar) e identificar alg: RS256
  3. Intentar forjar con HS256 usando la clave pública como secreto
  4. Si el servidor lo acepta → vulnerabilidad confirmada

Cómo corregirlo:

  • ❌ Vulnerable: jwt.decode(token, public_key)
  • ✅ Correcto: jwt.decode(token, public_key, algorithms=['RS256']) con whitelist explícita

Variante 2: JKU / X5U SSRF

El bug: los parámetros opcionales del header del JWT (jku, x5u) permiten indicar una URL externa desde donde buscar la clave de verificación. Un servidor que confía a ciegas hace una petición a una URL controlada por el atacante.

Explotación: el atacante agrega "jku": "https://atacante.com/jwks.json" en el header del JWT. El servidor hace un GET a la URL del atacante, recibe la clave pública del atacante, valida el JWT con ella → el atacante forjó un token válido.

Bonus: además del bypass de auth, se convierte en una SSRF chain. Agregar "jku": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" hace que el servidor solicite los metadatos internos de AWS. La respuesta va al parser de JWKS (el parseo falla, pero la petición ya se hizo). Metadatos de la nube exfiltrados.

Cómo detectarlo:

  1. Agregar jku al header de un JWT legítimo apuntando a https://burpcollaborator.net/
  2. Enviar la petición con el JWT modificado
  3. Si el collaborator recibe un ping → vulnerabilidad confirmada

Cómo corregirlo:

  • Allowlist explícita de las URLs JKU permitidas
  • Mejor: ignorar por completo los parámetros jku/x5u si no los usas (configurar la librería para deshabilitarlos)

Variante 3: kid Path Traversal

El bug: el parámetro kid (Key ID) se usa para seleccionar con qué clave validar el token. Un servidor que usa kid como path a un archivo del filesystem acepta path traversal.

Explotación clásica: el atacante envía "kid": "../../../../tmp/known_content". Si el atacante logra escribir en /tmp/known_content (vía upload de archivos, log poisoning, etc.), puede hacer que el servidor “lea” esa clave conocida y valide un JWT forjado con ella.

Variante más común en 2026: kid acepta SQL injection (el servidor consulta la clave en la DB). El atacante envía "kid": "1' UNION SELECT 'CLAVE_CONOCIDA' --".

Cómo detectarlo:

  1. Capturar un JWT, modificar kid a ../etc/passwd
  2. Si el error de validación cambia (file not found, parse error específico) → vulnerable
  3. Probar SQL injection en el kid (' OR 1=1 --)

Cómo corregirlo: el kid debe ser un ID opaco, validado contra una allowlist fija. NUNCA usado como path del filesystem o como string SQL.

Variante 4: expiración del token ignorada

El bug: más simple y más común de lo que parece. El servidor no valida el claim exp. Algunas librerías no lo validan por defecto — hay que pasar un flag explícito.

Un token robado en 2023 sigue válido en 2026. Sesión eterna. Es el tipo de bug que sobrevive a varios pentests porque a nadie se le ocurre probarlo (todos asumen que se valida).

Cómo detectarlo:

  1. Modificar exp en el payload a una fecha pasada
  2. Volver a firmarlo (si tienes el secreto de prueba) o usar un token expirado real
  3. Si el servidor lo acepta → vulnerable

Cómo corregirlo: pasar siempre options={'require': ['exp'], 'verify_exp': True} o el equivalente de la librería.

Por qué estas variantes siguen existiendo en 2026

Tres motivos prácticos:

1. Librerías antiguas en sistemas legados

Sistemas ERP, sistemas gubernamentales, sistemas legados de bancos usan librerías de 2017–2020 que todavía tienen los bugs. Migrar a una nueva versión major es un trabajo que nadie prioriza.

2. Configuraciones por defecto subóptimas

Los devs copian el ejemplo del README: jwt.decode(token, key). Sin leer que hay que pasar algorithms=['RS256']. CVE en producción esperando.

3. Code review sin foco en auth

El código de auth rara vez pasa por un code review especializado. El bug entra en un PR de una feature “menor” y nunca se ve. Todo equipo que no tiene una revisión de auth específica tiene al menos 1 de estas variantes en producción, estadísticamente.

Cómo probarlo tú mismo en 30 minutos

Usa jwt.io o una herramienta similar:

  1. Pega tu JWT en el decoder
  2. Verifica el alg en el header
  3. Modifícalo a none y vuelve a pegarlo — si el servidor lo acepta (probando contra un endpoint protegido), es vulnerable
  4. Si es alg: RS256 — intenta HS256 usando la clave pública como secreto (variante 1)
  5. Agrega jku al header apuntando a un servidor tuyo — si el servidor hace la petición, variante 2 confirmada
  6. Modifica exp a una fecha pasada — si lo acepta, variante 4

5 pruebas. 30 minutos. Si alguna pasa — CVE abierto en producción.

Configuración segura completa

El patrón correcto que toda implementación de JWT debería seguir:

  • Whitelist de algoritmos: algorithms=['RS256'] o equivalente, sin fallback
  • Claims obligatorios: options={'require': ['exp', 'iat']}
  • Validación activa: verify_exp, verify_iat, verify_nbf, verify_aud, verify_iss todos en True
  • Audience esperada: audience='api.objetivo.com'
  • Issuer esperado: issuer='https://auth.objetivo.com'
  • Sin jku aceptado. Sin kid que acepte un path. Sin fallback de algoritmo. Sin expiración opcional.

La lección más importante

Los 4 patrones de arriba no son bugs oscuros. Cada uno aparece en pentest con regularidad. La diferencia entre un equipo vulnerable y un equipo seguro no es el talento — es el proceso:

  • Code review enfocado en auth
  • Pentest periódico que valida específicamente estos patrones
  • Actualización de librerías (PyJWT, jsonwebtoken siempre en la última major)
  • CI con pruebas automatizadas de la validación de JWT

Si tu aplicación usa JWT (y casi todas lo usan en 2026), vale la pena dedicar 30 minutos a probar tú mismo los 4 vectores antes de descubrirlo por las malas.

Para hablar de un pentest enfocado en auth y JWT, mira las páginas dedicadas: pentest para API y pentest para SaaS. Para entender otros patrones de auth que dominan el account takeover en 2026, mira OAuth Account Takeover.

Compartir

WhatsAppLinkedInX

Siguiente paso

¿Quieres aplicar esto a tu sistema?

No Vuln hace pentests en profundidad con la misma metodología descrita en este artículo. Solicita una propuesta: NDA bilateral en 24h y alcance definido en una llamada técnica.