🔑 JSON Web Token (JWT)
Explicación acerca de los JSON Web Tokens (JWT)
Copyright © 2025 Okta.
📋 Índice de Contenido
📋 Índice
💡¿Qué son los JWT?
Un JWT (JSON Web Token) es un estándar abierto usado para transmitir información de manera segura entre dos partes en formato JSON.
Generalmente se utiliza en procesos de autenticación y autorización. Por ejemplo, cuando un usuario inicia sesión, el servidor le entrega un token para que en cada petición posterior no tenga que volver a enviar usuario y contraseña.
Un JWT es autocontenido, lo que significa que dentro del propio token ya viaja toda la información necesaria, firmada o cifrada, de modo que el receptor puede verificar su validez.
Antes de analizar las vulnerabilidades que se pueden presentar al usar JWT, conviene entender su estructura y los algoritmos que se emplean para firmarlos o cifrarlos.
🏗️Estructura de un JWT
La estructura de un JWT está compuesta por tres partes:
-
Header: contiene metadatos como el algoritmo de cifrado (
alg), el identificador (kid), la clave pública para descifrar (jwk) o la posibilidad de obtener claves públicas válidas desde una URL (jku). -
Payload: incluye los llamados claims, que son básicamente pares clave/valor. Al igual que el header, está codificado en base64url (similar a base64, pero sin los caracteres
+,/y sin padding=). Aquí suele ir la información más concreta sobre el token, como el usuario al que pertenece o los privilegios que posee. -
Firma: se calcula a partir del header y el payload codificados en base64url, separados por un punto. Puede utilizar algoritmos de cifrado simétrico o asimétrico.
- En cifrado simétrico, tanto el servidor que firma el token como el que lo valida comparten la misma clave secreta.
- En cifrado asimétrico, el token se firma con una clave privada y se valida con la correspondiente clave pública.
En ambos casos, la firma garantiza la integridad y autenticidad del JWT.
🔒Cifrado simétrico
En el cifrado simétrico:
- El usuario se autentica en el servidor.
- El servidor genera un JWT, lo firma usando su clave privada o secreto y lo devuelve al cliente (generalmente dentro de la cabecera
Cookie). - En cada petición posterior, el cliente envía ese token.
- El servidor lo verifica usando la misma clave secreta.
- Si la verificación es exitosa, el token se considera válido y el usuario obtiene acceso a los recursos que le corresponden.
🔐Cifrado asimétrico
En el cifrado asimétrico:
- El usuario se autentica en el servidor.
- El servidor genera un JWT, lo firma con su clave privada o secreto y lo devuelve al cliente.
- En cada petición posterior, el cliente envía ese token.
- El servidor lo verifica usando la clave pública correspondiente.
- Si la verificación es correcta, el token se considera válido y el usuario puede acceder a los recursos autorizados.
Al igual que en otras secciones de este blog, utilizaremos los laboratorios de PortSwigger para llevar a cabo los ejemplos prácticos.
🧪Laboratorios
En caso de que queráis ver la resolución directa de algún laboratorio, podéis utilizar el siguiente índice:
- Lab 1: JWT authentication bypass via unverified signature
- Lab 2: JWT authentication bypass via flawed signature verification
- Lab 3: JWT authentication bypass via weak signing key
- Lab 4: JWT authentication bypass via jwk header injection
- Lab 5: JWT authentication bypass via jku header injection
- Lab 6: JWT authentication bypass via kid header path traversal
- Lab 7: JWT authentication bypass via algorithm confusion
Dado que los ataques sobre los JWTs no se clasifican estrictamente en grupos, sino más bien en diferentes formas de enfoque, abordaremos la resolución de los laboratorios de manera progresiva.
Para facilitar la resolución de los laboratorios, es conveniente instalar la extensión de BurpSuite llamada JWT Editor.
Al igual que en todos los laboratorios de esta sección, se nos proporcionarán las siguientes credenciales wiener:peter.
Además, la forma de proceder será siempre la misma: autenticarnos y recargar una petición a cualquier recurso de la página web para así visualizar nuestro JWT.
Lab 1
Omisión de la autenticación JWT mediante firma no verificada
En este laboratorio, al igual que en la mayoría de los ejercicios anteriores, se nos solicita obtener un JWT válido para el usuario administrador y utilizarlo para borrar al usuario Carlos del sistema.
Se nos indica que no se verifica la firma del JWT. Por lo tanto, podemos interceptar la petición, abrir el JWT Editor y modificar el campo sub. En lugar de wiener, ponemos administrator y copiamos el nuevo JWT.
A continuación, pegamos este JWT en nuestras cookies en la página web. Como no se está verificando la firma de ninguna manera, podemos autenticarnos como el usuario administrador y proceder a borrar a Carlos.
Lab 2
Omisión de la autenticación JWT mediante una verificación de firma defectuosa
Al igual que en los laboratorios anteriores, se nos solicita borrar al usuario Carlos.
En esta ocasión, se nos indica que la verificación de la firma del JWT no se realiza correctamente.
En lugar de seguir el procedimiento del laboratorio anterior, podemos modificar el algoritmo (alg) a none y copiar todo el JWT, excepto la parte de la firma, es decir, solo el header y el payload.
De esta manera, es posible generar un JWT válido para el usuario administrador.
En este caso, el método funciona correctamente, ya que la validación del JWT no es completa.
Lab 3
Omisión de la autenticación JWT mediante una clave de firma débil
Al igual que en los laboratorios anteriores, se nos solicita borrar al usuario Carlos.
En este caso, se nos indica que la clave secreta es débil. Esto representa un problema, ya que al ser un cifrado simétrico, si conseguimos adivinar la clave, podemos firmar cualquier JWT y será considerado válido, dado que la verificación se realiza con la misma clave.
Si tomamos un JWT y lo analizamos con Hashcat, que permite realizar ataques de fuerza bruta, obtenemos que el secreto es secret1.
A partir de este punto, podemos utilizar BurpSuite para generar una nueva firma con este secreto para el usuario administrador. Como la firma se crea y verifica con la misma clave, el JWT será considerado válido.
Lab 4
Omisión de la autenticación JWT mediante la inyección de encabezados jwk
Al igual que en los laboratorios anteriores, se nos solicita borrar al usuario Carlos.
En este caso estamos ante una clave asimétrica, es decir, que el token se ha firmado con un secreto (clave privada) pero se valida con una clave pública.
Si probamos todo lo aprendido en los laboratorios anteriores, veremos que nada funciona, por lo que hay que pensar en un enfoque distinto. Dentro de los pares clave/valor que se pueden incluir en el header de un JWT, existe el campo jwk, en el cual se especifica la clave pública con la que se ha cifrado el token.
Si esto no se valida correctamente, puede ser un problema, ya que nosotros podríamos:
- Crear un JWT con nuestra clave privada para el usuario administrador.
- Incluir en el propio JWT la clave pública correspondiente dentro de
jwk. - Si no está controlado, el servidor usará esa clave para validar el JWT.
Justamente esto es lo que se utiliza para resolver el laboratorio.
Lo que tenemos que hacer en BurpSuite es:
- Crear una nueva clave asimétrica para el usuario administrador.
- Copiar la clave pública dentro del header del JWT.
Lab 5
Omisión de la autenticación JWT mediante la inyección de encabezados jku
Al igual que en los laboratorios anteriores, se nos solicita borrar al usuario Carlos.
La forma de enfocar este laboratorio es exactamente la misma que en el caso anterior: firmar el JWT con nuestro secreto y pasarle al servidor la clave pública para que lo valide.
No obstante, esta vez la cabecera jwk está siendo sanitizada y no es vulnerable. Sin embargo, no solo podemos escribir la clave pública en la cabecera jwk, sino que también podemos alojarla en un servidor externo y referenciarla mediante la cabecera jku.
Esto es lo que usaremos para resolver el laboratorio:
- Utilizar el exploit server de PortSwigger para almacenar la clave pública.
- Añadir la cabecera
jkupara hacer referencia a ese exploit server de la siguiente manera:
Lab 6
Omisión de la autenticación JWT mediante path traversal del parámetro kid
Al igual que en los laboratorios anteriores, se nos solicita borrar al usuario Carlos.
En este caso, tenemos un JWT firmado con clave simétrica, es decir, que se cifra y valida con el mismo secreto.
Aquí entra en juego la cabecera kid, que se utiliza para especificar un identificador del token. Este identificador normalmente sirve para buscar la clave asociada dentro de un directorio donde se almacenan todas las claves disponibles para validar los tokens existentes.
El inconveniente aparece cuando el valor de kid no está bien sanitizado, ya que se puede usar para realizar directory path traversal y apuntar a rutas que no deberían ser accesibles.
Esto es justo lo que ocurre en el laboratorio:
- El servidor usa el valor de
kidpara buscar una clave dentro de un directorio. - Al no estar sanitizado, se vuelve vulnerable a path traversal.
- Sin embargo, simplemente recorrer directorios hacia atrás haría que el servidor intentase usar otro archivo como clave para validar el token, lo cual no sería efectivo, a menos que se use un archivo como
/dev/null.
El archivo /dev/null contiene un byte nulo. Esto resulta útil porque:
- Podemos firmar nuestro certificado simétrico con un byte nulo (
AA==en Base64). - Luego, mediante path traversal en el
kid, apuntamos a/dev/null. - De esta forma, al validar el token, coinciden tanto el secreto usado como el valor devuelto por el archivo.
Todo esto ser vería de la siguiente manera:
Lab 7
Omisión de la autenticación JWT mediante confusión de algoritmos
Al igual que en los laboratorios anteriores, se nos solicita borrar al usuario Carlos.
En este caso, se nos indica que existe una ruta expuesta en la URL donde se muestra la clave pública usada para validar los JWTs.
Buscando en Internet, se observa que la ruta por defecto para los JWT suele ser jwks.json.
Si hacemos un curl a esa ruta, podemos ver la clave pública:
curl -s "https://0a94009a03948ade870fe20100da0047.web-security-academy.net/jwks.json" | jq
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "bbf1c06f-7cd2-4c8d-9e47-d13249ba50aa",
"alg": "RS256",
"n": "tL3Ez9m_4LvsZFjnJU-2zAl8tbFf0vZpwoRZSBHbs9PCv9kve2PfQAjZmutgIR2ZjSR3Yr0tOLsFC9hkZsfN5ThseZCydlkWZcUTYB_wDDI3c7XBzTbxDqFv27rfJkXtk0aZkQjMKff7qs25304sz3H5DLOKGBBPsUbT4qpK1HaDD368Vh3pHcq-niqaQsOd-leO0PPOPdtHwDnnkIpLhTH2kBb2egFeyEfh0NWwxeFpj55XVVh4zrnbHuY___oqqhDzLYe81agdW4330ZQKTqs8poI8yhsv0LgHquHx0UVxrXcnictX2yjMZI99peHWlDsenDtviMG1wItiKdlwQQ"
}
]
}
En este caso, el algoritmo es asimétrico, por lo que la clave expuesta no permite firmar un nuevo JWT y hacerlo válido, sin embargo, se pueden probar otras técnicas.
La condición que nos va a permitir resolver este laboratorio es que un servidor no esté bien configurado y use la misma clave para validar algoritmos asimétricos y simétricos. Esto significa que si recibe un token asimétrico lo validará con la clave pública, pero si recibe uno simétrico lo validará con la clave privada, que en este caso será la misma. En otras palabras, si el servidor utiliza la misma clave para validar asimétrico y simétrico, la clave que hemos obtenido es la pública de un algoritmo asimétrico, pero también la privada de un simétrico.
Por ello, lo que podemos hacer es ir a BurpSuite, crear una nueva clave asimétrica RS256 y en la parte de clave pública pegar la del servidor de la siguiente manera:
A continuación, nos copiaremos la clave pública en formato PEM de esa nueva clave RS256 (es decir, la clave pública del servidor), la codificaremos en Base64 y crearemos una nueva clave simétrica, donde en el valor de K (key/secreto) pondremos la clave pública en formato PEM codificada en Base64.
Con esto, lo que estamos indicando es que en nuestra clave simétrica el secreto es la clave pública del servidor, y como el servidor va a usar esta misma clave para validarlo debido a su mala configuración, hemos creado un JWT válido.
Ahora solo queda ir al JWT original, cambiar el campo alg a HS256 y firmarlo con nuestra clave simétrica.