Saltar al contenido

Mejores Prácticas de Seguridad de JWT | Ataque alg none / Vencimiento / Verificación de Firma

Categoría: Autenticación / Seguridad

JWT (JSON Web Token) es el estándar de facto para la autenticación en API web modernas, pero implementarlo mientras se malinterpreta la especificación puede introducir fácilmente vulnerabilidades. Este artículo explica patrones de mal uso común de JWT y mejores prácticas para evitarlos.

Repaso de la Estructura de JWT

JWT es un formato que concatena tres cadenas codificadas en Base64URL con puntos.

eyJhbGci...    .   eyJzdWIi...     .    SflKxwRJSM...
   header              payload                signature
  • header: Algoritmo de firma (alg) y tipo de token (typ)
  • payload: Objeto JSON de reclamaciones (sub / iss / aud / exp / iat / ...)
  • signature: Firma HMAC / RSA / ECDSA sobre header y payload

Puede verificar el contenido de un JWT usando DevLab JWT Decoder. Recuerde que Base64URL es codificación, no encriptación, por lo que el contenido del payload puede ser leído por cualquiera sin verificación de firma del servidor.

Amenaza 1: ataque alg=none

La especificación JWT incluye "alg": "none", una designación de algoritmo "sin firma". Si un atacante explota esto para modificar el payload con un encabezado {"alg":"none"}, y la biblioteca lo acepta tal cual, se convierte en una vulnerabilidad crítica que permite la suplantación de cualquier usuario.

Contramedida:

  • Enumerar explícitamente en lista blanca los algoritmos permitidos durante la verificación
  • Pasar como una matriz como jwt.verify(token, secret, { algorithms: ['HS256'] })
  • Las bibliotecas alrededor de 2015 eran vulnerables, pero las bibliotecas principales actuales ya han implementado soluciones. Sin embargo, siempre use la versión más reciente a menos que esté creando desde cero.
// ✗ 悪い例 (アルゴリズム未指定 = ライブラリが alg ヘッダを信頼)
jwt.verify(token, secret);

// ✓ 良い例 (アルゴリズムを固定)
jwt.verify(token, secret, { algorithms: ['HS256'] });

Amenaza 2: ataque de confusión de claves (key confusion)

Un atacante que posee una clave pública RSA puede cambiar el algoritmo de RS256 a HS256 para forzar que la clave pública sea tratada como una "clave secreta". Si la biblioteca JWT confía en el campo alg al seleccionar la función de verificación, los tokens falsificados firmados con HMAC usando la clave pública serán aceptados.

Contramedida: Siempre fija el algoritmo en el lado de la verificación (misma contramedida que la amenaza 1). Además, distingue explícitamente el tipo de clave:

  • Para HS256, pasa como Buffer
  • Para RS256, pase la clave pública en formato PEM

Amenaza 3: Expiración no verificada / tokens indefinidos

El claim exp en JWT indica el tiempo de vencimiento en segundos UNIX, pero a menudo se ignora durante la validación. Si un token emitido una vez se puede usar indefinidamente, el daño de una violación no se puede contener.

Contramedida:

  • Establezca exp en una duración corta al emitir (15 minutos a 1 hora es lo recomendado para tokens de acceso)
  • Siempre verifique exp durante la verificación (las bibliotecas principales lo hacen automáticamente)
  • Para sesiones de larga duración, use el patrón de token de actualización (token de acceso de corta vida + token de actualización de larga vida + lista de revocación del lado del servidor)

Amenaza 4: Almacenar información sensible en JWT

El payload de JWT es simplemente codificación Base64, y cualquiera puede decodificarlo. A pesar de esto, las implementaciones que ponen contraseñas o números de tarjeta de crédito en el payload continúan apareciendo.

Contramedida:

  • Incluir solo información de identificación mínima en el payload indicando 「este usuario es…」 (sub / user_id / role)
  • Almacenar información confidencial en una base de datos del lado del servidor y recuperarla de JWT usando user_id
  • Si debe enviar información sensible en un JWT, use JWE (JWT cifrado)

Amenaza 5: Incapaz de revocar (logout)

Cuando JWT se emite sin estado, no se puede revocar. Incluso si un usuario cierra sesión, como el servidor no tiene información del token, el token "se puede usar hasta el vencimiento". Incluso después de un cambio de contraseña, los JWT emitidos siguen siendo válidos.

Contramedida:

  • Minimizar la ventana de daño con exp corto (15 minutos o menos)
  • Al cerrar sesión, registre el jti (JWT ID) en la lista de denegación (denylist) del lado del servidor y compárelo durante la validación
  • Para eventos críticos (cambio de contraseña, cambio de permiso), almacene token_version en el registro del usuario y verifique una coincidencia durante la validación

Amenaza 6: Secretos débiles

Si el secreto HS256 es demasiado corto, puede ser descifrado por fuerza bruta. Cadenas como "secret" y "password123" son particularmente vulnerables.

Contramedida:

  • HS256 debe usar al menos 256 bits (32 bytes) de valores aleatorios
  • Generar con openssl rand -base64 32 o la herramienta de generación de contraseñas
  • Nunca confirme secretos en Git. Adminístrelos con variables de entorno o Secrets Manager

Resumen de mejores prácticas

  1. Fijar el algoritmo en el lado de verificación (algorithms: ['HS256'])
  2. El secreto HS256 debe tener 256 bits o más, RS256 debe usar RSA con 2048 bits o más
  3. El exp del token de acceso es de 15 minutos a 1 hora
  4. Implementar sesiones de larga duración con patrón de token de actualización
  5. No incluir información sensible en el payload (Base64 no es cifrado)
  6. Al cerrar sesión, registre jti en la lista de denegación
  7. Gestione secretos con variables de entorno o Secrets Manager; nunca los confirme
  8. Almacene JWT en una cookie HttpOnly, no en localStorage, en el lado del cliente (contramedida XSS)

Herramientas útiles para depuración

Resumen

JWT es un token de autenticación conveniente si se usa correctamente, pero sin conocer los peligros de especificación y los patrones de ataque, corre el riesgo de introducir vulnerabilidades en sistemas de producción. Comprenda las 6 amenazas y contramedidas presentadas en este artículo y use la última versión de las bibliotecas JWT como base. Le recomendamos que revise regularmente su implementación y monitoree los avisos de seguridad.

❓ Preguntas frecuentes

¿Dónde debe guardarse un JWT en el navegador?
Evita localStorage: es legible en el instante en que aterriza un XSS. La respuesta por defecto es una cookie con HttpOnly, Secure y SameSite. Las cookies traen el CSRF a escena, pero SameSite=Lax más una comprobación de token en las peticiones que cambian estado lo cierran. No hay que elegir entre un riesgo y el otro.
¿Sigue siendo real la amenaza del ataque alg:none?
Las bibliotecas principales ya lo rechazan por defecto, pero reaparece en cuanto escribes código que lee alg de la cabecera y elige con él el verificador. El arreglo es simple: fija en la verificación el algoritmo que el servidor espera y no confíes nunca en el alg que declara el token. La misma regla evita la confusión de pasar una clave pública RS256 a un punto que espera HS256.
Una caducidad corta desconecta a los usuarios demasiado a menudo.
La forma estándar son dos tokens: uno de acceso de vida corta, de unos pocos minutos, y uno de refresco de vida larga. El token de acceso no se puede revocar, así que caduca de forma natural; el de refresco se guarda en el servidor y puede anularse en cualquier momento. Esa separación es lo que permite que un cierre de sesión o un cambio de permisos surta efecto de inmediato.