Breaking Token JWT or JWT Exposed !


¿Qué es un TOKEN JWT?

Antes de nada vamos a explicar de que está compuesto un Token JWT a través de una revisión de las diferentes fuentes web grafícas. 
Os dejo los diferentes enlaces a lo largo del texto, para que podáis consultarlos. 

El funcionamiento de un Token JWT es el siguiente:  
"El usuario se autentica en nuestra aplicación, bien con un par usuario/contraseña, o a través de un proveedor como puede ser Twitter, Facebook o Google por ejemplo. 
A partir de entonces, cada petición HTTP que haga el usuario va acompañada de un Token en la cabecera (Authentication: ). 
Este Token no es más que una firma cifrada que permite a nuestro API identificar al usuario. Pero este Token no se almacena en el servidor, si no en el lado del cliente (por ejemplo en localStorage o sessionStorage) y el API es el que se encarga de descrifrar ese Token y redirigir el flujo de la aplicación en un sentido u otro. 
Como los tokens son almacenados en el lado del cliente, no hay información de estado y la aplicación se vuelve totalmente escalable. 
Podemos usar el mismo API para diferentes apliaciones (Web, Mobile, Android, iOS, ...) solo debemos preocuparnos de enviar los datos en formato JSON y generar y descrifrar tokens en la autenticación y posteriores peticiones HTTP a través de un middleware
También nos añade más seguridad. 
Al no utilizar cookies para almacenar la información del usuario, podemos evitar ataques CSRF (Cross-Site Request Forgery) que manipulen la sesión que se envía al backend. Por supuesto podemos hacer que el token expire después de un tiempo lo que le añade una capa extra de seguridad".

Bueno hasta este momento, la vida es maravillosa y todo es de color de rosa, pero vamos a profundizar un poco más.
El JWT consta de 3 partes separadas por puntos y codificadas en base64 (codificación != encriptación ) el Header, el Payload y la Signature, veamos que significa cada uno:

String completo:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTAsInJvbCI6ImFkbWluIn0.Qrif0tQhNCnlL6iP3xoPz4vzpTtuQsfjkRjpe86gLZU

El Header  es la cabecera del token, que a su vez tiene otras dos partes, el tipo, en este caso un JWT y la codificación utilizada.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 =  {"typ":"JWT","alg":"HS256"}

El Payload está compuesto por los llamados JWT Claims donde irán colocados la atributos que definen nuestro token.

  1. sub: Identifica el sujeto del token, por ejemplo un identificador de usuario.
  2. iat: Identifica la fecha de creación del token, válido para si queremos ponerle una fecha de caducidad. En formato de tiempo UNIX
  3. exp: Identifica a la fecha de expiración del token. Podemos calcularla a partir del iat. También en formato de tiempo UNIX.

Estos atributos pueden variar, podéis usarlos o no, la recomendación es usarlos, pero como todo en la vida, es mi recomendación.

¿Qué contiene el nuestro?

eyJpZCI6MTAsInJvbCI6ImFkbWluIn0 = {"id":10,"rol":"admin"} 

La firma, Signature, es la tercera y última parte del JSON Web Token. Está formada por los anteriores componentes, Header y Payload, codificados en Base64 y encriptada con una clave secreta que debe estar almacenada en nuestro backend. Así sirve de Hash para comprobar que todo está bien.
En nuestro ejemplo sería:

Qrif0tQhNCnlL6iP3xoPz4vzpTtuQsfjkRjpe86

Si lo descodificáis sale algo ilegible así que no los ahorramos.

*Antes de continuar, decir que la parte teórica anterior es de https://carlosazaustre.es/blog/que-es-la-autenticacion-con-token/ que no quiero pisar el trabajo de nadie, ya que él lo explica bastante bien.

¿Y ahora qué?

Aquí es donde empieza el salseo.
Hace un tiempo salieron varias vulnerabilidades en JWT y entre otras la rotura de hmacsha256. ¿Entonces? Si podemos tener la clave secreta podremos crear token válidos para la aplicación y así poder validarnos con cualquier usuario.
¿Sin user ni pass? El user normalmente lo tienes dentro del Payload (aunque no lo necesitas) y la pass no la necesitas teniendo un token válido.
¿Cómo se hace esto? Vamos a verlo. Para ello he hecho este pequeño POC que va a simular lo explicado.

Lo primero es montar un framework el que queráis, en mi caso codeignither, e integrar JWT (podéis ver: https://github.com/ParitoshVaidya/CodeIgniter-JWT-Sample ), haciendo una API REST sencilla que genere un Token JWT y si le pasas un token te dice si es valido o no.

Lo primero generamos un token válido haciendo una petición GET a la API:


Bien, teniendo el token podemos descodificar la parte del Header y del Payload. 

Vamos ahora a modificarlo con https://jwt.io/ y ha intentar validarlo:


Como vemos, ya nos sale en rojo sanguinolento "Invalid Signature". Por tanto debemos conseguir la clave secreta, pero antes comprobemos nuestra API a ver si modificando y validando nos tirara una petición legítima. Por tanto cambiamos el rol por Pepe:


Se hace directamente en la web, y metemos el token en nuestra API mediante una petición POST y el Token en la cabecera AUTHORIZATION:


Como era de esperar nos peta, ya que no ha sido creado con la clave secreta de nuestro servidor, por lo tanto no va a ser válida. Para que sea válida debemos de obtener la clave, para ello vamos a obtenerla por fuerza bruta sin diccionario.

Vamos a usar una herramienta llamada jwt-cracker descargada de (https://www.npmjs.com/package/jwt-cracker), metemos el string completo de la siguiente manera: 

* En nuestro caso sabemos que la longitud es de 4 caracteres en minúsculas, sino se conociera por defecto usa 12, pero nos llevaría más tiempo.


Le damos al enter. En este caso en pocos segundos lo saca:


Se ve claramente la contraseña secreta obtenida y entonces ahora mediante la web https://jwt.io/ podemos crear el token válido.

Ahora, lo hacemos como antes pero metiendo la clave correcta y ponemos de nuevo Pepe:


Validamos en nuestra API el token 


Ahora si vemos como el token fue validado y obtenemos el rol de Pepe, y así de fácil es, también tenemos esta herramienta para ataques por diccionario, por si por fuerza bruta se os eternizara: https://github.com/Sjord/jwtcrack .

Solución para que no pase esto, utilizar algoritmos RS256 (RSA Signature with SHA-256) o superior para generar el token JWT , ya que ha día de hoy todavía es seguro, y JWT tiene soporte para ello. 

Bueno espero que este POC os haya gustado y sirva para vuestras pruebas/auditorias, siempre dentro de un entorno controlado y de pruebas xDDD

Un saludo. 

Comentarios

  1. Excelente articulo, muchas gracias

    ResponderEliminar
  2. Gracias por el post! Me ha parecido muy interesante :)

    ResponderEliminar
  3. Un pregunta. Arranco un wireshark en una red, interceptando el trafico, capturo el token de unh usuario y a partir de ese momento puedo hacerme pasar por éste. No es esta una vulnerabilidad de JWT importante? como se protege uno contra ello
    Gracias, Daniel.

    ResponderEliminar
    Respuestas
    1. Excelente pregunta, pero creo con el https resolverias esa vulnerabilidad

      Eliminar

Publicar un comentario