JWT authentication bypass via kid header path traversal
kid path traversal: el servidor lee la clave de verificación del filesystem usando el valor del header kid, permitiendo apuntar a /dev/null y firmar con el secreto vacío.
Target Overview & Vulnerability Summary
El servidor usa JWT para las sesiones y, para verificar la firma, utiliza el parámetro kid del header para recuperar la clave desde su sistema de archivos. Como el kid es controlable y no se valida, se puede forzar al servidor a usar cualquier archivo arbitrario como clave de verificación. Usando path traversal hacia /dev/null (un archivo vacío), la clave de verificación es una cadena vacía y se firma el token con ese mismo secreto.
| Campo | Valor |
|---|---|
| Severidad | Alta (bypass de autenticación → GET /admin y eliminación de carlos) |
| Endpoint vulnerable | POST /login, GET /admin |
| Root cause | kid (controlable por el atacante) usado directamente como ruta de archivo para obtener la clave de verificación, sin saneamiento de path traversal |
Technical Breakdown & Exploitation Flow
El laboratorio usa un algoritmo de encriptación simétrica (HS256). El JWT emitido tras el login con wiener:peter:
{
"kid": "dd8b2c75-ee9a-4955-8d0b-a7b29fa97455",
"alg": "HS256"
}
{
"iss": "portswigger",
"exp": 1757265493,
"sub": "wiener"
}
El enunciado describe que para verificar la firma el servidor usa el parámetro kid del header para fetch de la clave desde el filesystem del servidor web. Ese valor es una ruta del sistema de archivos, por lo que un atacante puede forzar al servidor a utilizar un archivo arbitrario como clave de verificación.
Paso 1 — Elegir el archivo objetivo y derivar el secreto
/dev/null es un archivo vacío: recuperarlo devuelve una cadena vacía. Por tanto, si el token se firma con un secreto igual al contenido del archivo (la cadena vacía, un null byte), la firma será válida para el servidor.
Codificación del null byte a base64 (formato en el que el secreto se representa en las herramientas de JWT):
printf '\0' | base64
Resultado:
AA==
Paso 2 — Crear la clave simétrica con el secreto vacío
Con el plugin JWT Editor: crear una New Symmetric Key, modificar el campo k por el null byte codificado en base64 (AA==) y guardar.
Paso 3 — Path traversal en kid y firma
En Repeater, sobre la request con el JWT, modificar el header kid con un path traversal hacia /dev/null y mantener alg: HS256:
{
"kid": "../../../../../../../../../../dev/null",
"alg": "HS256"
}
Firmar el token con la clave simétrica creada (el secreto vacío). A partir de ahí, probar enviando requests y firmando con cada cambio. Verificar que la ruta de la solicitud GET no apunte a un usuario como wiener: usar directamente /admin o /my-account sin parámetros. Finalmente DELETE /admin?username=carlos.
Root Cause Analysis
El defecto es la interpolación directa del kid en una ruta de filesystem. El verificador interpreta kid como una ubicación de archivo y lo concatena sin validar componentes de path traversal (../). /dev/null resuelve a un archivo existente y legible cuyo contenido es vacío, de modo que la clave de verificación efectiva es el secreto vacío, conocido por el atacante:
// defectuoso: kid controlado por el atacante → ruta de archivo
keyPath := filepath.Join(keyDir, header.Kid) // "../../../../../../../../../../dev/null"
secret, err := os.ReadFile(keyPath) // lee /dev/null → []byte{}
sig, _ := hmac.New(sha256, secret) // firma con secreto vacío
Secure Remediation
- Nunca usar el
kidcomo ruta del filesystem. El verificador debe mantener un mapeo autorizadokid → claveen memoria o en un keystore, sin tocar el filesystem:
key, ok := keyStore[header.Kid] // mapa estático kid → clave
if !ok {
return errors.New("unknown kid")
}
sig, _ := hmac.New(sha256, key)
- Validar el formato del
kid(p. ej. UUID o identificador alfanumérico con regex) y rechazar cualquier valor con../, barras o separadores de ruta. - Si es imprescindible leer de disco, resolver
filepath.Cleany comprobar que el resultado quede dentro del directorio de claves (deny path traversal). - Limitar la longitud y el juego de caracteres permitidos del
kid.