Parte: 4 — Seguridad de aplicaciones web · Fuente: Bug Bounty Bootcamp (Vickie Li) / RFC 7519 ⏱️ Duración estimada: 110 min · Nivel: Avanzado
Auditar la seguridad de los JSON Web Tokens (JWT), hoy omnipresentes en APIs y SPAs. Aprenderás a decodificarlos, detectar implementaciones inseguras y explotar fallos clásicos: alg:none, confusión de algoritmos, claves débiles y falta de verificación de firma.
⚠️ Ética: solo en labs propios/autorizados. Forjar tokens de sistemas ajenos es un delito.
Al finalizar, el alumno podrá:
alg:none y la aceptación de firma vacía.| # | Tema | Por qué importa |
|---|---|---|
| 1 | Estructura y claims de JWT | Base para atacar |
| 2 | Algoritmos: HS256 vs. RS256 | El eje de varios ataques |
| 3 | alg:none |
Bypass de firma |
| 4 | Confusión de algoritmos | Firmar con la clave pública |
| 5 | Claves HMAC débiles | Crackeo offline |
| 6 | Claims: exp, iss, aud, kid | Fallos de validación |
| 7 | Defensa: verificar firma y alg | Cierre del fallo |
Un JWT (JSON Web Token) es una forma de sesión sin estado en el servidor: en lugar de guardar
un session ID que apunta a datos en el servidor (clase 102), el token contiene los datos
—quién eres, tus permisos, cuándo expira— y una firma que garantiza que no se han manipulado. Se
compone de tres partes separadas por puntos y codificadas en Base64URL: el header (qué algoritmo
firma), el payload (los claims: sub, exp, iss, aud, roles) y la firma. El punto
crítico que hay que grabar desde el principio: el payload NO está cifrado, solo codificado —
cualquiera lo lee decodificando el Base64—; lo único que la firma garantiza es la integridad, no
la confidencialidad. Poner un secreto en el payload de un JWT es un error clásico.
La seguridad de un JWT depende por completo de que el servidor verifique la firma correctamente, y aquí están los dos fallos históricos que toda esta clase enseña a reconocer.
alg:none. La especificación de JWT incluyó un algoritmo "ninguno" para tokens sin firmar.
Algunas librerías, mal implementadas, aceptaban un token cuyo header decía alg: none sin
verificar nada —el atacante quita la firma, pone none, cambia el payload a admin y entra—. Es
el fallo más grave y más simple, y la razón por la que el servidor nunca debe permitir que sea
el token quien decida el algoritmo.
Confusión de algoritmos (RS256 → HS256). RS256 firma con clave asimétrica (privada firma,
pública verifica) y HS256 con clave simétrica (el mismo secreto firma y verifica). El ataque:
el atacante toma la clave pública RS256 (que es pública, la conoce) y firma un token diciendo que
usa HS256 con esa clave pública como secreto. Si el servidor, al ver HS256, verifica usando su
clave RSA pública como secreto HMAC, la firma cuadra, y el atacante ha forjado un token válido con
material que era público. La causa es de nuevo dejar que el token elija el algoritmo.
El tercer ataque es más directo: si el JWT usa HS256 con un secreto débil, se puede crackear
offline con hashcat (clase 080) —el token lleva su propia firma, así que es un objetivo de
cracking perfecto— y, una vez conocido el secreto, forjar cualquier token. La defensa es un secreto
largo y aleatorio, como cualquier clave. En cuanto a los claims, hay que validarlos todos, no
solo la firma: exp (que no haya caducado), iss (emisor esperado), aud (destinado a esta
aplicación); ignorar exp deja tokens válidos para siempre. Cuidado especial con kid (key ID),
que indica qué clave usar: si el servidor lo usa sin validar, puede ser vector de inyección
(SQLi, path traversal) o apuntar a una clave que el atacante controla.
La defensa se resume en reglas tajantes: verificar siempre la firma, fijar el algoritmo
esperado en el servidor (no leerlo del token), rechazar alg:none, usar secretos fuertes,
validar todos los claims, y recordar que un JWT no se puede revocar fácilmente antes de su
expiración —lo que obliga a tiempos de vida cortos y a una lista de revocación si se necesita cerrar
sesiones de golpe—. Ese último punto es el compromiso de fondo del JWT frente a la sesión clásica:
gana en escalabilidad sin estado, pierde en control de revocación.
alg:none: algoritmo que indica "sin firma". Característica: si el servidor lo acepta, cualquiera forja tokens.alg.kid (key id): cabecera que indica qué clave usar. Característica: inyectable (path traversal, SQLi) si no se valida.exp: expiración del token. Característica: si no se valida, los tokens no caducan.| Término | Definición concisa |
|---|---|
| JWT | Token que contiene sus datos y una firma; sesión sin estado |
| Header / payload / firma | Las tres partes del JWT, en Base64URL |
| Claim | Dato del payload: sub, exp, iss, aud, roles |
| Payload no cifrado | Solo codificado; cualquiera lo lee |
| Integridad, no confidencialidad | La firma protege de manipulación, no de lectura |
| HS256 | Firma simétrica; el mismo secreto firma y verifica |
| RS256 | Firma asimétrica; privada firma, pública verifica |
| alg:none | Algoritmo "ninguno"; aceptarlo permite tokens sin firma |
| Confusión de algoritmos | Firmar HS256 con la clave pública RS256 como secreto |
| Secreto HMAC débil | Crackeable offline; permite forjar tokens |
| exp / iss / aud | Claims que hay que validar además de la firma |
| kid | Key ID del header; vector de inyección si no se valida |
| Fijar el algoritmo | El servidor decide el algoritmo, no el token |
| Revocación de JWT | Difícil antes de expirar; exige TTL cortos o lista negra |
git clone https://github.com/ticarpi/jwt_tool && cd jwt_tool && pip install -r requirements.txt
⚠️ Solo en labs propios.
"alg":"none", elimina la firma y modifica un claim (p. ej. role: admin).hashcat -m 16500 token.txt wordlist.txt
kid para apuntar a un archivo/valor controlado.exp: reusa un token caducado.Resuelve un lab de JWT de PortSwigger (alg:none, clave débil o confusión de algoritmos) y escala a administrador forjando un token. Criterio de aceptación: el lab queda resuelto, entregas el token forjado, el ataque usado y la corrección (verificar firma, fijar el algoritmo esperado, secreto fuerte).
| Síntoma / mensaje | Causa y cómo arreglar |
|---|---|
| alg:none rechazado | Servidor valida el algoritmo; prueba otro ataque |
| Confusión no funciona | La librería fija el alg; documenta como fortaleza |
| hashcat no crackea | Secreto fuerte; solo funciona con claves débiles |
| Token modificado rechazado | La firma sí se verifica; revisa el vector |
| kid no explotable | Se valida contra allowlist; correcto |
❓ ¿JWT es inseguro? No por sí mismo. Los fallos vienen de implementaciones que no verifican bien la firma o el algoritmo.
❓ ¿Puedo revocar un JWT? No fácilmente por ser stateless. Se usan expiraciones cortas y listas de revocación o rotación de claves.
❓ ¿Guardo el JWT en localStorage o cookie? Cookie HttpOnly reduce el robo vía XSS; localStorage es accesible por JS. Cada opción tiene trade-offs de CSRF/XSS.
Clase 102 — Gestión de sesiones y ataques asociados