Ataques a JWT (JSON Web Tokens): qué son, cómo se explotan y cómo se corrigen
· 10 min de lectura
Aprende cómo funciona un JSON Web Token, por qué un fallo al verificar la firma permite hacerse pasar por cualquier usuario, cuáles son los ataques clásicos (alg none, secreto débil, confusión de algoritmos) y cómo validar los tokens de forma segura.
Cuando inicias sesión en una aplicación moderna o en una API, es muy probable que el servidor te entregue un JWT (JSON Web Token): una cadena larga con dos puntos en medio que tu navegador o tu aplicación envía en cada petición para demostrar quién eres. Es cómodo, es estándar y lo usan miles de servicios.
Pero un JWT solo es seguro si el servidor comprueba correctamente su firma. Cuando esa comprobación falla, o cuando la clave que firma los tokens es débil, un atacante puede fabricar su propio token diciendo «soy el administrador», y el servidor se lo creerá.
En esta lección vas a ver cómo está construido un JWT, por qué la firma lo es todo, cuáles son los ataques clásicos, cómo se detectan y explotan en un laboratorio autorizado y cómo se validan los tokens de forma segura.
Practica estas técnicas solo en sistemas propios o en entornos autorizados, como los laboratorios de whoami-labs. Falsificar tokens para acceder a cuentas ajenas sin permiso es un delito.
Qué es un JWT#
Un JWT tiene tres partes separadas por puntos: encabezado.contenido.firma. Las dos primeras son objetos JSON codificados en Base64URL, que es una forma de convertir datos en texto, no un cifrado: cualquiera puede leerlas.

Si decodificas el encabezado y el contenido de un token típico, ves algo así:
{"alg": "HS256", "typ": "JWT"}
{"sub": "1031", "usuario": "estudiante", "rol": "usuario", "exp": 1767225600}
- El encabezado indica el algoritmo de firma (
alg). - El contenido (payload) lleva los datos, llamados claims: quién es el usuario (
sub), su rol, cuándo caduca el token (exp). - La firma se calcula con el encabezado, el contenido y una clave secreta que solo conoce el servidor.
La seguridad del sistema depende de una idea sencilla: si alguien cambia "rol": "usuario" por "rol": "admin", la firma deja de coincidir y el servidor debe rechazar el token. Nadie sin la clave puede producir una firma válida.
Algoritmos de firma#
| Tipo | Algoritmos | Cómo funciona |
|---|---|---|
| Simétrico (HMAC) | HS256, HS384, HS512 | La misma clave secreta firma y verifica |
| Asimétrico (RSA) | RS256, RS384, RS512 | Se firma con una clave privada y se verifica con la pública |
| Asimétrico (curvas elípticas) | ES256, ES384 | Igual que RSA, con claves más cortas |
Por qué ocurre: la causa raíz#
Casi todos los ataques a JWT nacen de uno de estos tres problemas:
- El servidor no verifica la firma, o la verifica solo a veces.
- El servidor confía en el algoritmo que dice el propio token, en lugar de exigir el que él espera.
- La clave secreta es débil y se puede adivinar.
Este código Node.js ilustra el primer error, uno de los más comunes:
// Vulnerable: decode NO comprueba la firma, solo lee el contenido
const jwt = require("jsonwebtoken");
function usuarioActual(req) {
const token = req.headers.authorization.split(" ")[1];
return jwt.decode(token); // cualquiera puede escribir lo que quiera aquí
}
decode solo convierte el Base64 en JSON. Quien use esta función para decidir permisos está aceptando tokens que el propio atacante puede escribir a mano.
Los ataques clásicos#
| Ataque | Qué hace el atacante | Fallo que aprovecha |
|---|---|---|
| Firma no verificada | Modifica el contenido y deja cualquier firma | El servidor usa decode en lugar de verify |
| alg: none | Cambia el algoritmo a none y elimina la firma | La librería acepta tokens sin firma |
| Secreto débil | Adivina la clave HMAC por fuerza bruta y firma tokens nuevos | Clave corta o de diccionario |
| Confusión de algoritmos | Cambia RS256 por HS256 y firma con la clave pública | El servidor deja que el token elija el algoritmo |
| Cabeceras kid, jku o jwk | Hace que el servidor use una clave elegida por el atacante | El servidor confía en cabeceras del propio token |
| Sin caducidad | Reutiliza un token robado indefinidamente | Tokens sin exp o sin revocación |
El algoritmo none#
La especificación de JWT incluye el algoritmo none para tokens sin firma. Algunas librerías antiguas lo aceptaban aunque el servidor esperara tokens firmados. El ataque consiste en cambiar el encabezado a {"alg": "none"}, modificar el contenido y dejar la firma vacía (el token termina en punto). Las librerías modernas lo rechazan por defecto, pero sigue apareciendo en implementaciones caseras.
La confusión de algoritmos#
Con RS256, el servidor verifica con su clave pública, que no es secreta. Si el servidor deja que el token decida el algoritmo, un atacante puede cambiar alg a HS256 y firmar el token usando esa clave pública como si fuera un secreto HMAC. Un servidor mal programado verificará con la misma clave pública y, por tanto, aceptará la firma.
Cómo se detecta#
Pruebas manuales#
- Localiza el token: suele ir en la cabecera
Authorization: Bearer ...o en una cookie. - Decodifica el encabezado y el contenido (por ejemplo con la extensión JWT Editor de Burp Suite) y anota el algoritmo y los claims que deciden permisos.
- Prueba, siempre en un entorno autorizado, si el servidor detecta cambios:
| Prueba | Qué buscas |
|---|---|
| Cambiar un claim (rol, usuario) sin tocar la firma | Si se acepta, la firma no se está verificando |
| Eliminar la firma por completo | Si se acepta, el servidor ignora la firma |
Cambiar alg a none y vaciar la firma | Si se acepta, la librería admite tokens sin firmar |
Revisar si existe exp y su duración | Tokens eternos o de vida muy larga |
| Probar el secreto contra diccionarios (solo HS256) | Claves débiles o por defecto |
Herramientas#
- Burp Suite con la extensión JWT Editor: para decodificar, modificar y volver a firmar tokens.
- jwt_tool: herramienta especializada que automatiza las pruebas más comunes en laboratorios.
- hashcat (modo 16500) o John the Ripper: para probar secretos HMAC contra diccionarios.
- Revisión de código: busca
decodeusado para autorización, listas de algoritmos permitidos ausentes y secretos escritos en el código.
Explotación paso a paso en un laboratorio#
Vamos a recorrer el caso más frecuente en laboratorios: una API que firma sus tokens con HS256 y un secreto débil.
Paso 1: obtener y leer tu token#
Inicia sesión con tu cuenta de prueba y copia el token de la cabecera Authorization. Decodifica las dos primeras partes:
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDMxIiwicm9sIjoidXN1YXJpbyJ9.FIRMA"
echo "$TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null; echo
El contenido muestra "rol": "usuario". Si consiguieras cambiarlo a admin con una firma válida, tendrías acceso de administrador.
Paso 2: comprobar si la firma se verifica#
Antes de nada, prueba lo más sencillo: cambia el rol en el contenido, vuelve a codificarlo y envía el token con la firma original. Si la API lo acepta, la firma no se comprueba y no necesitas más. Si lo rechaza, la firma sí se verifica y toca averiguar la clave.
Paso 3: probar el secreto contra un diccionario#
Con HS256, la firma depende de una clave secreta. Si es débil, se puede encontrar probando palabras de un diccionario:
# Guarda el token completo en un archivo y prueba el diccionario rockyou
echo "$TOKEN" > token.txt
hashcat -a 0 -m 16500 token.txt /usr/share/wordlists/rockyou.txt
Si hashcat encuentra la clave, ya puedes firmar tokens válidos como lo haría el propio servidor.
Paso 4: fabricar un token de administrador#
Con la clave encontrada, genera un token nuevo con el rol cambiado. Por ejemplo, en Python con la librería PyJWT:
import jwt # pip install pyjwt
contenido = {"sub": "1031", "rol": "admin"}
print(jwt.encode(contenido, "CLAVE_ENCONTRADA", algorithm="HS256"))
Paso 5: usar el token falsificado#
curl -s http://IP_DEL_LAB/api/admin/usuarios \
-H "Authorization: Bearer TOKEN_FALSIFICADO"
Si la API responde con datos de administración, has escalado privilegios sin conocer ninguna contraseña. En laboratorios como TOKEN FORGE, ese es el camino para llegar a la bandera.
Qué puede lograr un atacante#
| Impacto | Ejemplo real |
|---|---|
| Suplantar a cualquier usuario | Cambiar el sub para entrar en otra cuenta |
| Escalar privilegios | Cambiar el rol a administrador |
| Acceso persistente | Reutilizar tokens robados que nunca caducan |
| Compromiso de todas las cuentas | Con la clave secreta, firmar tokens para cualquier usuario del sistema |
Cómo se corrige#

Verificar siempre, con el algoritmo fijo#
Usa la función de verificación de una librería mantenida, nunca la de decodificación, y fija el algoritmo que esperas en lugar de leerlo del token:
// Seguro: verify con algoritmo fijo, emisor y audiencia esperados
const datos = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ["HS256"],
issuer: "https://api.ejemplo.com",
audience: "app-web",
});
# Seguro con PyJWT
datos = jwt.decode(token, CLAVE, algorithms=["HS256"], audience="app-web",
options={"require": ["exp", "sub"]})
En PyJWT la función se llama decode, pero verifica la firma cuando le pasas la clave y la lista de algoritmos: lo importante es no desactivar esa verificación.
Claves fuertes y bien guardadas#
- Para HS256, usa una clave aleatoria y larga (al menos 256 bits), generada con un generador criptográfico, nunca una palabra.
- Guárdala en variables de entorno o en un gestor de secretos, nunca en el código ni en el repositorio.
- Si varios servicios deben verificar los tokens, considera RS256 o ES256: solo el emisor tiene la clave privada.
Caducidad y revocación#
- Tokens de acceso de vida corta (minutos) y tokens de renovación con controles adicionales.
- Exige el claim
expy comprueba tambiéniss(emisor) yaud(audiencia). - Ten un mecanismo para invalidar tokens comprometidos, por ejemplo una lista de identificadores revocados (
jti) o rotando la clave.
No confiar en cabeceras del propio token#
Ignora o restringe con lista blanca las cabeceras kid, jku y jwk: la clave de verificación la decide el servidor, nunca el token.
Errores comunes al defenderse#
- Usar decode para autorizar. Leer el token no es verificarlo.
- Aceptar cualquier algoritmo. Si no fijas
algorithms, abres la puerta anoney a la confusión de algoritmos. - Secretos de ejemplo. Claves como
secret,changemeo el nombre de la empresa caen en segundos con un diccionario. - Guardar datos sensibles en el contenido. El payload es legible por cualquiera; no pongas contraseñas ni datos personales innecesarios.
- Tokens eternos. Un token robado sin caducidad sirve para siempre.
Preguntas frecuentes#
¿Un JWT está cifrado?#
No. Un JWT normal (JWS) solo está firmado: cualquiera puede leer su contenido, pero nadie puede modificarlo sin invalidar la firma. Existe una variante cifrada (JWE), mucho menos común.
¿Es mejor HS256 o RS256?#
Los dos son seguros si se usan bien. HS256 es más simple cuando un solo servicio firma y verifica. RS256 o ES256 convienen cuando varios servicios deben verificar sin poder firmar.
¿Dónde debo guardar el token en el navegador?#
Una cookie con HttpOnly, Secure y SameSite evita que un XSS lo robe con JavaScript. Si lo guardas en localStorage, cualquier XSS en tu web podrá leerlo.
¿Qué es el modo 16500 de hashcat?#
Es el modo de hashcat para probar claves de JWT firmados con HMAC (HS256, HS384, HS512). Solo sirve para tokens simétricos; con RS256 no hay secreto que adivinar.
¿Cómo practico de forma legal?#
En los laboratorios TOKEN FORGE y MATRIX de whoami-labs, enlazados al final de esta lección, que reproducen estos fallos de forma controlada.
Resumen#
- Un JWT tiene encabezado, contenido y firma; las dos primeras partes son legibles por cualquiera.
- Toda su seguridad depende de que el servidor verifique la firma con una clave fuerte y un algoritmo fijo.
- Los ataques clásicos son: firma no verificada,
alg: none, secretos débiles, confusión de algoritmos y cabeceraskid/jkumanipuladas. - Se corrige con verificación estricta, claves aleatorias largas, caducidades cortas, revocación y sin confiar en cabeceras del token.