CVE-2020–28413 / Blind SQL Injection en Mantis Bug Tracker 2.24.3 API SOAP.
--
Hola a tod@s.
En esta oportunidad veremos una vulnerabilidad de Blind SQL Injection en la API SOAP de la aplicación Mantis BugTracker en su versión 2.24.3. Antes de empezar, es valido aclarar que la información que será entregada a continuación, es entregada con fines EDUCATIVOS e INVESTIGATIVOS y que no me hago responsable del mal uso de la información por parte de los lectores, una vez aclarado esto continuamos. Dicha vulnerabilidad se presentaba en la funcionalidad mc_project_get_users, y su detección es tan solo modificando y enviando el parámetro “access” sin ningún valor y cambiando el tipo de valor a String. Hay que tener en cuenta que para explotar dicha vulnerabilidad es necesario tener un usuario dentro del sistema; para efectos de prueba usaremos un usuario de rol informador.
Se puede evidenciar que al realizar el envió del parámetro “access” sin ningún valor, el sistema esta retornando un mensaje de error SQL en donde nos dice que hay un error en la sintaxis y nos devuelve el comando SQL entero.
Teniendo en cuenta esto, podemos intentar inyectar alguna condición en el query SQL para que nos retorne un resultado valido en el sistema, así que inyectaremos un valor y seguido la condicional “or 1=1” para ver como se comporta.
Podemos ver claramente que al ingresar una condición en el query SQL, es posible retornar todos los usuarios. Ahora vamos a revisar que es lo que esta pasando en el código fuente de la aplicación.
Podemos evidenciar que la funcionalidad esta recibiendo los parámetros de autenticación y esta haciendo una validación si el usuario logueado en realidad puede realizar dicha función. En la linea 1301 vemos que la aplicación esta haciendo uso de los parámetros “p_project_id” y “p_access” enviándolos a otra funcionalidad hasta el momento desconocida.
Analizando la funcionalidad “project_get_all_user_rows” la cual recibe los parámetros de “project” y “access” enviados por la funcionalidad anterior, vemos que entre las primeras líneas el sistema reasigna el valor de la variable “access” a otra variable.
Líneas mas abajo podremos ver que hay una condicional en donde se pregunta si el valor de la variable “t_global_access_level” es un array, y se ejecutan una serie de condiciones anidadas guardando en una variable llamada “t_global_access_clause” parte del query SQL. De no ser así, se ejecuta una sola acción que es guardar en dicha variable la concatenación del string “ >=” y del valor de la variable “t_global_access_level”. Finalmente se ingresa la variable “t_global_access_clause” en el query SQL y luego se ejecuta.
Si realizamos un pequeño debug en el código y consultamos como se esta formando el query SQL antes de ser ejecutado, veremos lo siguiente.
Podemos observar como se esta formando la sentencia SQL al realizar una impresión de la variable “t_query”. Como vimos anteriormente, al no ser un array el valor enviado en la variable “access”, este esta simplemente agregando el string “>=” y luego el valor tal cual es enviado por el usuario.
Al mover el print luego de la ejecución del query, podremos leer el valor de la variable “t_result” la cual contiene el valor de la ejecución de la sentencia SQL.
Al realizar la impresión del valor de esta variable podremos ver la sentencia final que fue ejecutada en la base de datos, en la parte inferior del query veremos como fue inyectada la condicional enviada por el usuario. Luego de esta consulta, el sistema esta capturando solamente el ID del usuario retornado y realiza otra consulta interna a la base de datos con este valor para así retornar los datos finales.
Si lo vemos de una manera grafica, este seria el diagrama en donde la primer linea es la petición realizada por el usuario enviando los datos de acceso y de consulta, en esa misma linea, la aplicación realiza la validación del login y de ser correcta envía los parámetros a la siguiente función. En la segunda linea vemos que la base de datos retorna el “id”, “username”, “realname ”y “nivel de acceso” del usuario. Sin embargo, la aplicación realiza otra nueva petición con el id del usuario recién retornado. Finalmente, la base de datos retorna el “id”, “username”, “realname ”y el “email ”del usuario consultado y la aplicación responde con el formato XML correspondiente.
Vemos que al enviar un union select en el query, este se nos refleja en la respuesta SQL. Es importante mencionar que si se envía un string en lugar de algún valor numérico, el valor retornado siempre será 0. También es importante saber que el primer valor del union select , debe de ser un numero que no choque con el id de un usuario real en la base de datos, es decir, si el primer valor del union select es 1, este se referencia con el id del usuario 1 por lo que retornara un valor verdadero. Para efectos de muestra, se coloco el numero 900 como primer valor del union select ya que no se tiene tal cantidad de usuarios, pero en caso de tenerlos es mejor colocar un numero mas grande. Esto también podría servir para enumerar los ID de los usuarios del sistema .
Subiendo un poco el nivel al query, utilizaremos dicha vulnerabilidad para conocer la cantidad de usuarios en la aplicación. Para esto concatenamos un string con el count de los users de dicha tabla y lo posicionamos como el primer valor del union select. Es importante tener en cuenta que el string a concatenar debe de ser el caracter “-” ya que si se concatena con una letra o con otro símbolo no suele reconocerlo como tal y no se ejecuta la inyección de manera correcta.
Ahora, subiendo aun mas el nivel del query, vamos a intentar adivinar cada uno de los caracteres de los hashes de las contraseñas de los usuarios. Para esto realizamos el siguiente query SQL:
0 union all select (select if(substring((select binary(password) from mantis_user_table where id = A),B,1)=’C’,’0',’90000')), 2,3,4 order by id desc
Analizando dicho query vemos lo siguiente:
- select binary(password) from mantis_user_table where id = A: Esta sentencia nos devuelve el valor del hash de la contraseña de un usuario A.
- substring(query),B,1: En esta sentencia encontramos que se esta realizando un substring, es decir, capturar X cantidad de caracteres de una salida. En esta ocasión se esta capturando solamente el carácter numero B del query anterior en donde se extrae el hash de la contraseña de un usuario A.
- select if(query)=’C’,’0',’90000'): Por ultimo, encontramos esta función la cual nos compara un valor con otro y según ello da una respuesta. Esta respuesta es la que se coloca en la primer columna del union select, por lo que debe de ser un valor numérico y lo suficientemente alta o baja como para que no se cruce con el ID de un usuario como lo hablamos anteriormente. Así que esta función compara el caracter devuelto por la función Substring y lo compara con un valor que nosotros deseemos, el resultado de esta operación retornara un 0 si es verdadero o un 90000 si es falso.
En resumen, los parámetros A, B y C significan lo siguiente:
A = ID del usuario a quien extraer el hash de la password.
B = Posición del carácter que se quiere comparar.
C = Carácter a comparar
Ahora veamos un ejemplo practico.
Si vemos la base de datos, el usuario administrador identificado con el ID 1 tiene como inicio del hash de la contraseña los caracteres “e64b”. Así que vamos a realizar unas pruebas con dichos caracteres, para esto vamos a configurar los valores en el script anterior de la siguiente manera A=1, B=1, C=e, quedando de la siguiente manera.
Notemos que al realizar la petición de dicha consulta, se nos retorna un valor “0” en la respuesta xml ya que el primer caracter del hash es “e” y la esta comparando con el caracter “e”. Por tanto la condición es verdadera.
Si cambiamos la posición del caracter del hash que deseamos comparar (ahora es el valor “6”) pero no cambiamos el caracter con el que queremos contrastar (de momento sigue siendo “e”). Observamos que en la respuesta se nos entrego un “90000 ”indicando que dicha condición es falsa.
Pero si cambiamos el valor “C” (valor contrastado) al caracter que realmente es comparable proveniente del hash, el sistema nos retorna el valor “0” , indicando que la condición es verdadera.
Es así entonces, como con un poco de creatividad se puede realizar un script el cual recorra cada uno de los caracteres del hash y extraiga cada uno de ellos.
De igual manera el exploit fue publicado den exploitDB bajo el siguiente ID:
https://www.exploit-db.com/exploits/49340
Sin embargo, no es necesario irnos tan lejos con script desarrollados por el propio atacante. Ya que mediante herramientas automatizadas como SQLMap es posible aprovechar dicha vulnerabilidad y navegar por la data de la base de datos de manera mas libre.
Para efectos de esta prueba, solamente se extrajeron los nombres de las bases de datos, pero ya es de tu preferencia que tan lejos quieras llegar.
Cabe resaltar nuevamente que este post es realizado con fines EDUCATIVOS e INVESTIGATIVOS y que no me hago responsable del mal uso de dicha información por parte de los lectores.
El respectivo reporte realizado al equipo de Mantis BT esta en el siguiente link: https://www.mantisbt.org/bugs/view.php?id=27495
Resumen:
Producto: MantisBT
Proveedor: MantisBT Team
Versión vulnerable : 2.24.3
Tipo de vulnerabilidad: Blind SQL injection
CWE: CWE-89 / CWE-943
CVE: CVE-2020–28413
OWASP TOP 10: A1 web top 10 / Api8 api top 10
Mantis_ID: 0027495
Nivel de riesgo: Medio
CVSS 3.1: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
La versión 2.24.4 contiene la solución a dicha vulnerabilidad.
https://www.mantisbt.org/bugs/changelog_page.php?version_id=358
Aquí dejo también los links pertenecientes a CVE MITRE y NIST:
https://cve.mitre.org/cgi-bin/cvename.cgi?name=2020-28413
https://nvd.nist.gov/vuln/detail/CVE-2020-28413
Recuerda que cualquier duda ante el tema o alguna observación, son bienvenidas en la caja de comentarios o en mis redes sociales.
Hasta la próxima.