Aprende › Vulnerabilidades web

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.

Estructura de un JWT: encabezado y contenido legibles por cualquiera, y la firma que demuestra que nadie los modificó
Estructura de un JWT: encabezado y contenido legibles por cualquiera, y la firma que demuestra que nadie los modificó

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#

TipoAlgoritmosCómo funciona
Simétrico (HMAC)HS256, HS384, HS512La misma clave secreta firma y verifica
Asimétrico (RSA)RS256, RS384, RS512Se firma con una clave privada y se verifica con la pública
Asimétrico (curvas elípticas)ES256, ES384Igual 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:

  1. El servidor no verifica la firma, o la verifica solo a veces.
  2. El servidor confía en el algoritmo que dice el propio token, en lugar de exigir el que él espera.
  3. 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#

AtaqueQué hace el atacanteFallo que aprovecha
Firma no verificadaModifica el contenido y deja cualquier firmaEl servidor usa decode en lugar de verify
alg: noneCambia el algoritmo a none y elimina la firmaLa librería acepta tokens sin firma
Secreto débilAdivina la clave HMAC por fuerza bruta y firma tokens nuevosClave corta o de diccionario
Confusión de algoritmosCambia RS256 por HS256 y firma con la clave públicaEl servidor deja que el token elija el algoritmo
Cabeceras kid, jku o jwkHace que el servidor use una clave elegida por el atacanteEl servidor confía en cabeceras del propio token
Sin caducidadReutiliza un token robado indefinidamenteTokens 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#

  1. Localiza el token: suele ir en la cabecera Authorization: Bearer ... o en una cookie.
  2. 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.
  3. Prueba, siempre en un entorno autorizado, si el servidor detecta cambios:
PruebaQué buscas
Cambiar un claim (rol, usuario) sin tocar la firmaSi se acepta, la firma no se está verificando
Eliminar la firma por completoSi se acepta, el servidor ignora la firma
Cambiar alg a none y vaciar la firmaSi se acepta, la librería admite tokens sin firmar
Revisar si existe exp y su duraciónTokens 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 decode usado 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#

ImpactoEjemplo real
Suplantar a cualquier usuarioCambiar el sub para entrar en otra cuenta
Escalar privilegiosCambiar el rol a administrador
Acceso persistenteReutilizar tokens robados que nunca caducan
Compromiso de todas las cuentasCon la clave secreta, firmar tokens para cualquier usuario del sistema

Cómo se corrige#

Validación segura de un JWT: algoritmo fijo, firma verificada, caducidad corta y claims comprobados
Validación segura de un JWT: algoritmo fijo, firma verificada, caducidad corta y claims comprobados

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 exp y comprueba también iss (emisor) y aud (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 a none y a la confusión de algoritmos.
  • Secretos de ejemplo. Claves como secret, changeme o 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.

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 cabeceras kid/jku manipuladas.
  • Se corrige con verificación estricta, claves aleatorias largas, caducidades cortas, revocación y sin confiar en cabeceras del token.

Fuentes y lecturas recomendadas#

Practícalo en estos laboratorios

Ver todos los laboratorios de esta categoría