¿Es seguro mi JWT? Cómo se elude la verificación de un token
Muchos incidentes de seguridad con JWT no rompen la criptografía: explotan un verificador que acepta un token escrito por un atacante o que no verifica nada. El error de fondo es dejar que datos no verificados del token influyan en cómo el código lo comprueba. Esta guía explica los principales ataques y las reglas que los detienen.
Has verificado la firma y por eso te sientes seguro. La intuición es razonable, pero «verificar» y «verificar correctamente» no son lo mismo. Muchos incidentes de seguridad con JWT nacen de esa diferencia.
Hay un hecho clave: muchos incidentes de seguridad con JWT no rompen la criptografía. Los atacantes suelen explotar la configuración del verificador, el manejo de claves o la lógica de la aplicación, no revertir SHA-256 ni factorizar una clave RSA. Su objetivo es que tu código marque como válido un token creado por ellos.
Como explica el primer artículo, antes de comprobar la firma, la cabecera y el payload son entrada controlada por el atacante. La cabecera puede incluir campos como alg y kid que afectan al flujo de verificación. Un verificador que trata esos campos no verificados como órdenes, en lugar de como datos que debe comprobar, puede tomar una decisión de confianza equivocada. Una vez verificada la firma, la cabecera protegida queda cubierta por ella; el riesgo existe solo antes de verificar.
Por tanto, no basta con preguntar si JWT es seguro por naturaleza. La pregunta importante es si el verificador ha dejado al token decidir algo que corresponde al servidor. Los ataques siguientes comparten ese patrón: el token influye en una decisión que el verificador debe tomar por sí mismo. La defensa es sencilla: trata el token como datos que validar, no como instrucciones que ejecutar.
El problema central: por qué el verificador acepta un token que debería rechazar
Vuelve a las dos preguntas del primer artículo: qué dice el token y si debes confiar en él. La primera puede leerse directamente; la segunda exige verificación. Los ataques de este artículo apuntan a la segunda cuestión de cinco formas frecuentes:
- Saltar la verificación: hacer que el verificador concluya que no hay nada que verificar.
- Usar la clave equivocada: hacer que use una clave que el atacante puede obtener.
- Controlar la selección de clave: hacer que seleccione u obtenga una clave controlada por el atacante.
- Adivinar la clave: aprovechar un secreto débil en vez de eludir la verificación.
- Usar un token válido en otro servicio: el token es genuino, pero no fue emitido para ese servicio.
Cuatro de estos ataques no debilitan el algoritmo de firma: eluden o desvían el proceso de verificación. También hay un fallo aún más básico: la verificación no ocurre.
El error más básico: no verificar la firma
El error más común y fácil de pasar por alto es no llamar nunca a la función de verificación. En el código, leer un token y verificarlo pueden parecer operaciones muy similares. Muchas librerías ofrecen API independientes para decodificar y verificar: la decodificación lee las claims y puede devolver el payload completo, pero no lo autentica; solo la verificación comprueba la firma con la clave y las reglas esperadas. Si la aplicación lee user.role directamente de los datos decodificados, las pruebas pueden pasar sin revelar que se omitió la verificación de firma. Un atacante solo tendría que modificar el payload.
Esta es la conclusión más importante del primer artículo: decodificar no es confiar. Al revisar la seguridad de JWT, confirma primero que la aplicación llama a la verificación, entrega la clave correcta y toma decisiones posteriores a partir del resultado verificado. Si pegas cualquier token sin clave en el inspector de JWT, se decodifica como JSON legible, pero el estado sigue siendo «Decoded, not verified». Decodificar no es una decisión de confianza; tu backend debe mantener el mismo límite.
Ataque 1: omitir la verificación mediante alg: none
JWA define un JWS «no asegurado»: su alg es none y su firma está vacía. Solo está previsto para casos especiales en los que otra capa de protocolo garantiza la integridad. Si un verificador elige cómo verificar a partir del alg de la cabecera sin cotejar la lista permitida por el servidor y el tipo de clave, un atacante puede cambiar alg a none, eliminar la firma y construir cualquier payload. El verificador confunde «sin firma» con «sin necesidad de verificar» y acepta un token falsificado.
Una librería moderna y conforme rechaza un JWS no asegurado salvo que la aplicación lo permita expresamente. En la práctica, el riesgo suele venir de dependencias antiguas, envoltorios que ocultan la configuración del algoritmo o ajustes que desactivan esta protección. Que no haya error no demuestra que la verificación se haya completado de forma segura.
El verificador puede leer el alg de una cabecera aún no verificada, pero solo como dato para comparar. Debe continuar únicamente si coincide con los algoritmos y el tipo de clave configurados en el servidor; en caso contrario, debe rechazar el token, incluido none. Por el mismo motivo, el inspector de JWT de este sitio no ofrece un modo de verificación none.
Ataque 2: confusión entre algoritmo y tipo de clave
La confusión entre RS256 y HS256 trata una clave pública RSA como un secreto HMAC.
En RS256, el emisor firma con una clave privada RSA y el servicio verifica con la clave pública correspondiente. Esa clave pública puede distribuirse abiertamente. Si el verificador deja que el alg del token elija el algoritmo, un atacante puede cambiar RS256 por HS256 y calcular un MAC usando los bytes de la clave pública como secreto HMAC. Si el servicio también ejecuta HMAC de forma incorrecta con esa misma clave pública, el MAC coincide y se acepta el token falsificado.
La causa es no fijar el algoritmo y el tipo de clave en el servidor. Limita los algoritmos aceptados en la configuración y vincula cada clave a un único uso. Por ejemplo, una clave pública RSA puede verificar RS256, pero nunca debe ser un secreto HMAC de HS256. Las librerías modernas suelen exigir una lista explícita de algoritmos permitidos; una librería o envoltorio que aún los infiera de la cabecera sigue siendo un riesgo. El generador de claves RSA muestra por qué: una clave pública se comparte por diseño y no puede utilizarse de forma segura como secreto simétrico.
Ataque 3: búsqueda insegura de claves con kid, jku y x5u
Los dos primeros ataques hacen que el verificador use mal una clave que ya posee. Este grupo intenta influir en qué clave usa el verificador.
kid (key ID) localiza una clave candidata dentro de un conjunto, pero antes de verificar es texto controlado por el atacante. Usarlo directamente para construir una ruta de archivo o una consulta SQL o LDAP puede introducir traversal o inyección. Úsalo solo como índice opaco en un conjunto de claves fijo y de confianza o en un JWKS en caché. Rechaza un kid desconocido; no lo uses para construir rutas, consultas ni URL.
jku (JWK Set URL) y x5u (X.509 URL) pueden apuntar a material de clave remoto. Admitirlos no es inseguro por sí mismo; seguir a ciegas cualquier URL entregada por un token sí lo es. La mayoría de los servicios debería ignorarlos y usar solo metadatos de emisor preconfigurados. Si necesitas usarlos, limita los hosts de confianza, exige HTTPS y validación de certificados, y no envíes cookies ni credenciales. El inspector de JWT permite ver estos campos sin actuar sobre ellos: muestra la cabecera, pero no descarga sus URL. Si un token referencia un certificado X.509, usa el decodificador de certificados para revisar su sujeto y validez, en lugar de confiar en el destino de la cabecera.
Ataque 4: secretos HMAC débiles
A veces no hace falta ninguna técnica de evasión: el secreto HMAC es débil. La seguridad de HS256 depende del secreto compartido; valores como secret, password, changeme o el nombre de una aplicación son fáciles de adivinar. Con un token válido, un atacante puede calcular HMAC contra una lista de palabras sin conexión hasta encontrar una coincidencia. No hay límite de tasa ni bloqueo en el servidor. Si conoce el secreto, puede emitir cualquier token porque el secreto de verificación HMAC también sirve para firmar.
La longitud y la entropía no son lo mismo. RFC 7518 exige una clave HMAC al menos tan larga como la salida del hash: 256 bits para HS256. Pero un secreto largo escrito por una persona puede seguir siendo adivinable. Genera al menos 32 bytes aleatorios con un CSPRNG, codifícalos como requiera tu librería y guárdalos como secreto gestionado, nunca en el código fuente. El generador de contraseñas usa aleatoriedad de Web Crypto para cadenas de alta entropía en desarrollo o pruebas; las claves de producción deben seguir el proceso de gestión aprobado por tu plataforma.
También importa quién posee la clave. Si quien verifica no debe poder emitir tokens, o si la verificación cruza una frontera de confianza que no controlas por completo, prefiere firmas de clave pública (RS/ES/EdDSA). Una filtración del material de verificación no permitirá falsificar tokens. Puedes comprobar una clave candidata con el inspector de JWT, pero «Signature verified» solo indica que esa clave verifica ese token. No es motivo para probar claves de producción en un entorno no autorizado.
Ataque 5: falta de validación de audiencia, emisor o tipo
No se trata de una falsificación: el token tiene una firma válida y no ha caducado, pero el servicio actual no debería aceptarlo.
Una firma válida solo prueba que el token no se alteró después de firmarlo y que se verifica con la clave esperada. No demuestra por sí sola la identidad ni los permisos del llamante. Estos dependen de claims como iss, sub y aud, del tipo de token y de las reglas del servidor. Si omites aud e iss después de verificar la firma, puedes crear un delegado confundido (confused deputy): un token emitido legítimamente para el servicio A se reproduce ante el servicio B, y B lo acepta porque la firma es válida. Después de verificar la firma, compara aud y un iss de confianza de forma exacta; no uses prefijos ni expresiones regulares laxas para la audiencia.
El caso concreto del primer artículo es un ID token de OIDC presentado a una API que solo debe aceptar un access token. Como aud no siempre los separa, comprueba también el tipo de token. Los access tokens JWT que siguen RFC 9068 usan at+jwt; en otros casos, define y aplica el tipo o perfil que espera tu protocolo. Un endpoint que requiere un access token no debe aceptar un ID token, ni a la inversa. Credenciales distintas necesitan reglas de validación mutuamente excluyentes, no solo la audiencia.
Un token robado tampoco requiere falsificación. El verificador no puede distinguir a su titular legítimo de un atacante que lo copió de un log o de un dispositivo comprometido; ambos superan la verificación mientras siga vigente. Un jti (ID de token) no detiene la reproducción por sí solo. Solo ayuda con estado en el servidor, por ejemplo un almacén de ID consumidos o revocados que se comprueba atómicamente en cada uso. Los tokens vinculados al cliente (sender-constrained), como DPoP o TLS mutuo, unen el token a una clave del cliente mediante cnf. No invalidan automáticamente un token robado, pero reducen el valor de reproducir un token bearer copiado si la clave privada del cliente permanece protegida. También importan las vidas cortas y la revocación.
Construye un proceso de verificación fiable
En vez de aplicar parches aislados, crea un proceso de verificación en el que la configuración del servidor tome las decisiones de principio a fin:
- Resuelve las claves desde la configuración, no desde el token. Conoce de antemano las claves de firma de cada emisor — un JWKS URI fijado desde metadatos de emisor de confianza, o claves que tengas directamente. Usa el
kidde la cabecera solo para elegir entre esas; ante unkiddesconocido, refresca el JWKS una vez y, si sigue faltando, rechaza en vez de salir a buscar. Una rotación fallida debe fallar de forma cerrada. - Fija los algoritmos y vincúlalos a las claves. Acepta solo los algoritmos que configuraste, y solo con el tipo de clave al que cada uno está vinculado — las dos reglas de los ataques 1 y 2, aplicadas antes de cualquier cálculo de firma.
- Acota la entrada. Rechaza tokens sobredimensionados, y rechaza cualquier cabecera
critcuyas extensiones no entiendas — el estándar lo exige, y evita que un token exija un procesamiento que nunca aceptaste. - Verifica, y luego valida las claims. Solo después de que la firma cuadre, valida
expynbf(permitiendo una holgura de reloj modesta), luegoiss,audy eltypdel token, más cualquier claim que exija tu endpoint — comparando cada una exactamente. - Autentica en la puerta, autoriza en cada petición. Un token verificado está autenticado, no autorizado; aplica los permisos en el servidor en cada petición. Y cuando la autorización deba cambiar de inmediato — un rol revocado, una cuenta deshabilitada — no te apoyes en una claim
rolecongelada hasta que el token caduque; consulta el estado en vivo.
Algunos de estos patrones aparecieron primero como CVE de librería; otros surgen enteramente de la configuración de la aplicación y del código de búsqueda de claves. Apóyate en una librería JWT bien mantenida y mantenla parcheada en vez de escribir la verificación a mano, y después configúrala de forma estrecha. Mantén acotados los algoritmos aceptados, las fuentes de claves y el tamaño del token. Una superficie más pequeña deja menos rincones donde esconderse al próximo fallo.
Lista de refuerzo para producción
Estos ataques comparten una causa: el token influye en decisiones que debe tomar el código del servidor. Los siguientes ocho controles la abordan:
- Verifica de verdad. Llama a la función de verificación con una clave y actúa según su resultado;
decodeno esverify, y «se decodificó bien» no es una comprobación de seguridad. - Restringe el algoritmo. Lee el
algde la cabecera solo para compararlo con tu lista blanca configurada, y vincula cada clave a un único algoritmo; rechazanoney todo lo que no esté en la lista. - Nunca dejes que el token elija la ubicación de la clave. Resuelve el
kidcomo un índice opaco a claves preconfiguradas o a un JWKS fijado. Ignorajku/x5usalvo que fijes hosts de confianza, exijas HTTPS con validación de certificado y no envíes credenciales. - Mantén las claves públicas asimétricas como solo-verificación. El vínculo clave–algoritmo (regla 2) es lo que impide que una clave pública se acepte como secreto HMAC.
- Haz los secretos de alta entropía. Al menos 32 bytes de un CSPRNG, guardados como secreto gestionado; prefiere firmas de clave pública donde el verificador no deba poder emitir tokens.
- Valida las claims exactamente. Tras pasar la firma, comprueba
exp,nbf,iss,audy eltypdel token, comparando cada uno exactamente — una firma perfecta sobre un token destinado a otro, o del tipo equivocado, sigue siendo un rechazo. - Recuerda que autenticado no es autorizado. Aplica los permisos en el servidor en cada petición, y consulta el estado en vivo para todo lo que deba revocarse de inmediato.
- Asume que los tokens se filtran. Mantenlos de vida corta, respalda
jticon estado en el servidor para atrapar reproducciones, y considera tokens vinculados al cliente para APIs de alto valor — un token válido robado verifica sin problema.
Y una regla heredada del primer artículo: el payload lo puede leer cualquiera, así que nunca metas un secreto dentro. Para permisos que deban cambiar o revocarse de inmediato, no dependas solo de una claim de token de larga vida; usa vidas cortas, revocación o una comprobación de autorización en línea cuando el riesgo lo requiera.
No hace falta ser más listo que un criptógrafo. Deja que el verificador decida desde su propia configuración si verifica, qué algoritmos y claves acepta y de dónde provienen las claves. El token es evidencia que debes validar. Esto cierra las principales vías de omisión de validación, pero el robo de tokens, la autorización y la operación de claves aún requieren controles propios.
La verificación no resuelve un problema: un token real y válido en manos de un atacante. Los dos próximos artículos lo abordan por separado: primero dónde guardar un token en el navegador (localStorage, cookies y el equilibrio entre XSS y CSRF), y luego los access tokens de vida corta, la rotación de refresh tokens y la revocación. Una firma prueba que el token no fue alterado; su ciclo de vida limita el daño después del robo de un token real.