banner
Hogar / Noticias / JSON Web tokens (JWT): claves para usarlos de manera segura
Noticias

JSON Web tokens (JWT): claves para usarlos de manera segura

Jul 13, 2023Jul 13, 2023

Hace unas semanas unos compañeros de desarrollo nos transmitieron su preocupación acerca de la generación de JSON Web tokens (JWT) que estaban haciendo como parte de la integración de una nueva herramienta. Habían oído acerca de "bastantes" problemas de seguridad y querían que les ayudaramos a validar si los tokens emitidos eran correctos y cumplían con algunos requisitos de seguridad básicos.

Actualmente estamos trabajando en un proyecto que ayuda en la automatización de tests de seguridad, APICheck, que hemos publicado recientemente como 'open source'. APICheck consiste en un conjunto de pequeñas herramientas que se pueden interconectar mediante pipes para poder encadenar la ejecución de distintos tests sobre peticiones a APIs, así que nos pusimos manos a la obra con la generación de una herramienta que permitiera validar los tokens que emitían, jwt-checker, en la que hemos implementado la posibilidad de pasar las validaciones que comentaremos sobre los tokens. Más adelante mostraré un ejemplo de un test usando la herramienta.

JWT (JSON Web Token) es un estándar abierto (publicado en el RFC 7519) que define un método compacto y autocontenido para encapsular y compartir aserciones (claims) sobre una entidad (subject) de manera segura entre distintas partes mediante el uso de objetos JSON. Se puede confiar y verificar el contenido del token cuando este está firmado digitalmente (JWS, RFC 7515). La firma se puede generar usando claves simétricas (HMAC) o claves asimétricas (RSA o ECDSA). Adicionalmente los JWT pueden contener también datos cifrados (JWE, RFC 7516) para proteger datos sensibles, aunque este tipo de tokens no son objeto de este estudio.

Es importante resaltar que, por defecto, los tokens no están cifrados, y que la cadena que vemos es simplemente una serialización usando codificación base64url, que puede decodificarse fácilmente para ver el contenido JSON del token en claro.

La respuesta entonces a la pregunta inicial es ‘depende...’. Como ocurre con otras muchas tecnologías, JWT depende fuertemente de la configuración que se use en la generación y del buen uso y la correcta validación de los tokens en el consumo.

JWT (JSON Web Token) es un estándar abierto que define un método compacto y autocontenido para encapsular y compartir aserciones sobre una entidad de manera segura entre distintas partes mediante el uso de objetos JSON.

Comenzaremos viendo cuáles son los principales tipos de tokens y los principales casos de uso:

JWT permite el intercambio de datos de manera segura entre partes de manera más eficiente que otros estándares (SAML) debido a su menor tamaño, lo que lo hace ideal para los siguientes casos de uso:

Cada caso de uso tiene distinto destinatario (aplicación cliente y servicio de API), pero en el caso de que se ejerza control simultáneo sobre las dos, un único token puede ser usado para ambos casos.

A continuación vamos a enumerar las mejores prácticas cuando trabajamos con JWT, centrándonos sólo en la generación y la validación de los mismos.

Salvo muy contadas excepciones (para uso en el lado cliente para llevar información de sesión y datos para reconstruir interfaz de usuario) un token no debe emitirse sin firma. La firma es una protección básica que permite que los consumidores del token puedan confiar en él y asegurar que no ha sido manipulado.

A la hora de elegir el algoritmo de firma hay que tener en cuenta que los algoritmos de clave simétrica son vulnerables a ataques de fuerza bruta si la clave usada no es los suficientemente fuerte (nombres de mascotas y fechas de nacimiento tampoco valen para esto;-), con lo que hay que proporcionar suficiente complejidad si se eligen algoritmos de clave simétrica. Por otro lado los algoritmos de clave asimétrica simplifican la custodia de la clave, ya que esta sólo es necesaria en la parte servidora que genera el token.

Un token, una vez firmado, es válido para siempre si no hay una fecha de expiración (claim exp). En el caso de los "Access tokens", si alguien captura uno podrá tener acceso a la operativa permitida para siempre. El asignar identificadores (claim jti) a los tokens permite su revocación, en caso de compromiso del token es muy deseable tener la opción de poder revocarlo.

De cara a facilitar la gestión por parte de los consumidores de los tokens es necesario identificar el emisor (claim iss) y todos los posibles destinatarios (claim aud), de esta manera podrán identificar la clave de validación y comprobar que está dirigido a ellos. Como veremos más adelante, es una buena práctica que los consumidores del token validen estos datos.

Como comentamos al inicio los tokens no van cifrados por defecto, así que hay que tener cuidado con la información que se introduce dentro de ellos. Si fuera necesario incluir información sensible o secretos hay que usar tokens cifrados.

A modo de ejemplo aquí os muestro la ejecución de un test para comprobar algunas de las validaciones que hemos visto usando jwt-checker:

En este ejemplo vemos una de las capacidades de la herramienta APICheck, que es el encadenamiento de acciones. En este caso la primera realiza una petición para obtener un token, generando un objeto de intercambio que le pasa a la siguiente herramienta, en este caso nuestro validador, que comprueba varios de los aspectos antes mencionados.

La firma es el único medio de verificar que los datos contenidos dentro son de confianza. La primera validación que debemos hacer, después de verificar el formato, es que el token está firmado. Esta opción tiene que estar siempre activada, si no un atacante podría capturar un token, quitar la firma, modificarlo a su antojo y reenviarlo. No aceptar nunca un token con ‘alg: "none"’ en su cabecera. La mejor protección es validar siempre que el campo alg contiene un valor de entre un conjunto específico que definamos, cuanto más pequeño mejor.

Nunca debemos confiar en la inocuidad de la información recibida en la cabecera o en los claims, sobre todo si los vamos a usar para búsquedas en backends. Siempre debemos sanitizarlos antes de usarlos para evitar ataques de inyección, por ejemplo el valor del campo kid (identificador de clave) puede ser usado para la búsqueda en una base de datos (inyeccion SQL) y el valor de los campos jku (URL a la definición de una clave) y x5u (URL a la cadena de certificados de la clave) son URLs arbitrarias que pueden provocar ataques SSRF y que deberíamos validar contra una whitelist de URLs.

Antes de aceptar un token debemos verificar que está dirigido a nosotros (claim aud) y que ha sido emitido por la entidad esperada (claim iss), de esta manera reduciremos el riesgo de que un atacante use un token emitido para otro propósito pero cuya firma podamos verificar.

Cuando seleccionemos la clave para validar la firma debemos tener en cuenta no sólo el issuer si no también el algoritmo. Un atacante podría capturar un token que usa "RS512“ y modificarlo a “HS256", usando para firmar la clave pública del emisor (que es accesible). Si nosotros no hiciéramos esta validación podríamos aceptar como válido el token cuando realmente no lo es.

Sigue leyendo sobre