CVE-2019-14537 Api Authentication bypass via Type Juggling
En uno de los test de intrusión que se realizaban para un cliente se encontró que usaban la herramienta YOURLS en uno de sus dominios. Debido a esto, decidimos revisar el código publicado en GitHub para ver si encontrábamos alguna vulnerabilidad y así, poder utilizarla para acceder a la organización, y... ¡efectivamente!... algo se escondía tras sus métodos de autenticación que hizo que encontrásemos una vulnerabilidad en la API, lo cual nos permitía realizar acciones en YOURLS como ver estadísticas de uso o crear enlaces para, por ejemplo, iniciar una campaña de phishing.
Pero antes de adentrarnos en la propia vulnerabilidad, vamos a ver qué es Yourls.
Yourls es un acortador de enlaces de código abierto (https://github.com/YOURLS/YOURLS) que según podemos ver al hacer una simple búsqueda en Google es muy utilizado:
El código se puede encontrar en su repositorio en GitHub (https://github.com/YOURLS/YOURLS):
Yourls viene con una API por defecto que permite realizar diferentes acciones como crear enlaces o ver las estadísticas de los usuarios. Para acceder a esta API es necesario autenticarse pudiendo hacerse de diferentes métodos:
- Firma de tiempo limitado (Signature + timestamp)
- Firma
- Usuario y contraseña
Los tres métodos de autenticación son vulnerables a ataques de Type Juggling, pero es el último el que puede ser explotado con mayor facilidad.
Los ataques de Type Juggling se basan en los principios de identidad e igualdad de PHP. En PHP existen dos operadores de comparación, el operador de igualdad “==” y el operador de identidad “===”.
Dos operadores en una comparación son iguales si son idénticos o existe algún tipo común en el que sean idénticos. Por ejemplo, “0” == 0 es verdadero, ya que existe un tipo común, el tipo entero o el tipo char, en el que son idénticos. En cambio “0” === 0 es falso, ya que aunque son iguales, pues existe un tipo común en el que son idénticos, no son idénticos, ya que uno de los operadores es de tipo char y el otro de tipo entero.
De los tres métodos existentes el que nos interesa es el de firma de tiempo que tiene el siguiente código:
La función recorre todos los usuarios existentes y para cada uno realiza los siguientes pasos:
- Realiza el resumen MD5 del parámetro timestamp concatenado al secreto del usuario y comprueba si lo anterior coincide con el parámetro signature enviado en la petición.
- Comprueba que no hayan pasado más de 12 horas desde la emisión de la firma.
Si lo anterior se cumple, la petición estará autorizada.
Se puede comprobar que se hace uso del operador de igualdad (no del de identidad) por lo tanto, si se envía un signature que pueda ser interpretado como un cero (por ejemplo “0e22”, “0”, “0e9999”, etc.) y el resultado del MD5 es una cadena que pueda ser interpretada como un cero (por ejemplo 0e111111111111111111111111111111), la comparación será verdadera y la petición quedará autenticada.
Como el atacante controla parte de la información que se incluye al hacer el resumen MD5 (el timestamp), entonces es posible realizar miles de peticiones con distintos valores hasta encontrar un resumen del formato buscado. Saltándose así la comprobación de inicio de sesión.
La única limitación en principio es que la diferencia de tiempo entre el timestamp enviado y la hora actual no puede ser mayor a 12 horas. Sin embargo, no se comprueba que el valor introducido en el parámetro timestamp sea un número entero, pudiendo introducir números decimales. Variando la parte decimal del timestamp introducido hace que el resultado del MD5 sea completamente diferente y la diferencia de tiempo sea mínima.
Las probabilidades de que un hash MD5 dado empiece por “0e” y que el resto de los caracteres sean numéricos es la siguiente:
- La probabilidad de que un MD5 empiece por “0e” es de 1 entre 256, es decir:
- Los hashes md5 están codificados con base 16, por lo que sus caracteres pueden tomar valores de 0 a F. La probabilidad de que sea un número es de 10 entre 16, por lo tanto, la probabilidad de que los 30 caracteres últimos sean numéricos es de:
- La probabilidad total de que un hash MD5 empiece por “0e” y el resto de caracteres sean numéricos es de:
- Se tendrán que realizar una media de 340 millones de peticiones para poder saltar la autenticación. Hay que tener en cuenta que en los cálculos anteriores se ha despreciado la posibilidad de que un hash empiece por 00e o 000e, etc. Lo cual otorga mayor probabilidad al ataque. También, hay que tener en cuenta que se hace la comprobación dos veces en cada petición ya que comprueba las combinaciones de timestamp + signature y de signature + timestamp. Por lo tanto, existe el doble de posibilidades de encontrar un hash válido en cada petición. También, dependerá del número de usuarios que existan en el servidor, ya que se hace la doble comprobación para cada usuario que exista en YOURLS. Por lo tanto el número de peticiones necesarias será:
Siendo numUsuarios el número de usuarios que existen en el servidor YOURLS. En definitiva, si existe un solo usuario de YOURLS, se necesitarán de media 170 millones de peticiones para encontrar un timestamp que genere un hash válido.
Se ha desarrollado un script para encontrar un timestamp válido y saltar la función de autenticación. Se ha desarrollado en Python, utilizando sockets y un número de hilos configurable por el usuario. Además, las peticiones que se realizan son HEAD en lugar de GET, para acelerar el proceso.
Puedes encontrar el script en: https://github.com/Wocanilo/CVE-2019-14537
Una vez lanzado el script irá mostrando las peticiones que llevan y la velocidad en peticiones por segundo:
Una vez que se consigue un timestamp válido ya podemos realizar peticiones a la API sin estar autenticado:
Pues nada compañeros, eso es todo por hoy ¡Nos vemos en el próximo post!