Cuando una aplicación o un servicio requieren almacenar información de forma persistente, entonces es necesario usar una base de datos. Y aunque existen muchos tipos, las bases de datos relacionales gozan de una respetable popularidad. Una inyección SQL (también llamada SQLi) ocurre cuando el cliente puede controlar las consultas que se hacen a la base de datos de un servicio.
No está de más aclarar que existen múltiples implementaciones de SQL para diferentes sistemas gestores de bases de datos, aún así en este artículo, sólo utilizaremos ejemplos suponiendo un sistema que utiliza PostgreSQL. Sin embargo, los payloads equivalentes en otras implementaciones no suelen diferir mucho.
Hoy en día, la inyección SQL es considerada una vulnerabilidad antigua y no muy común, sin embargo, vale analizar su impacto potencial.
¿Cómo funciona una inyección SQL?
Supongamos, por ejemplo, que para obtener información de nombres y precios de productos desde una base de datos, el servidor hace la siguiente consulta:
SELECT nombre, precio FROM productos WHERE nombre LIKE '%<userinput>%';
Esta consulta nos daría como respuesta una lista de dos columnas con todos los productos cuyo nombre se parezca a la entrada del usuario. Por ejemplo, si <userinput> recibe el valor de CARAMELO, la respuesta incluiría todos los productos almacenados en la base de datos cuyo nombre contenga la palabra CARAMELO y su respectivo precio.
Si un usuario mal intencionado introduce:
CARAMELO%' UNION SELECT username, password FROM users; --
La consulta SQL original se transformaría en:
SELECT nombre, precio FROM productos; WHERE nombre LIKE '%CARAMELO%' UNION SELECT username, password FROM users; --%';
La respuesta a la consulta anterior, no incluiría únicamente la lista de productos y sus precios, sino que también incluiría los nombres de usuario y sus respectivas contraseñas, suponiendo que la tabla donde se almacena la información de los usuarios lleva el nombre de users.
No se debe ignorar que por lo general las bases de datos suelen almacenar información confidencial de las empresas como por ejemplo: Credenciales de acceso, permisos de usuario, información crítica, entre otros activos de valor.
Los sistemas gestores de bases de datos suelen almacenar la información de todas las tablas en una tabla especial, lo cual podría ser una característica de mucha utilidad para un cibercriminal:
-- PARA OBTENER INFORMACIÓN DE TABLAS SELECT * FROM information__schema.tables; -- PARA OBTENER INFORMACIÓN DE COLUMNAS SELECT * FROM information__schema.columns WHERE table_name = 'user_s3cr3t_t4bl33';
De esta manera, un atacante podría obtener información sobre las tablas de la base de datos y sus columnas sin la necesidad de tener que adivinar estas.
Inyección SQL a ciegas (Blind SQL injection)
En ocasiones, no se puede apreciar los resultados de las consultas en las respuestas HTTP, generalmente cuando el request resulta en un error (condición falsa). Este tipo de SQLi se conoce como SQLi a ciegas o Blind SQL Injections y se explota usando una condicional que genera una demora de tiempo cuando ocurre un error. De esta forma, es posible usar un payload como el de a continuación:
a';SELECT CASE WHEN (SELECT Count(username) FROM users WHERE username = 'administrator' AND Substring(password, 1, 1) > 'a') THEN Pg_sleep(10) ELSE Pg_sleep(0) END --
Lo que desencadenaría la siguiente consulta SQL:
SELECT nombre, precio FROM productos; WHERE nombre LIKE '%a'; SELECT CASE WHEN (SELECT COUNT(username) FROM users WHERE username = 'administrator' AND SUBSTRING(password, 1, 1) > 'a') THEN pg_sleep(10) ELSE pg_sleep(0) END
La inyección anterior valida si la primera letra del campo password para el usuario administrator es igual que a, si se cumple tal condición recibiremos una respuesta del servidor de forma instantánea.
Por el contrario, si la consulta no cumple con la condición, la respuesta tardará al menos 10 segundos en llegar, lo que significa que la primera letra es mayor que a y debemos probar con la letra siguiente.
De esta forma, un atacante irá desvelando, letra por letra, el valor del campo requerido.
Este tipo de inyección SQL puede ser automatizado utilizando la herramienta SQLMap.
De una inyección SQL a RCE (Remote Code Execution)
Un atacante no tan solo podría husmear en una base de datos, si no que también podría conseguir ejecutar código de forma remota (RCE).
Por ejemplo, podría usar una inyección SQL para ejecutar la siguiente instrucción:
COPY (SELECT '<?php shell__exec(<command>); ?>') TO '/tmp/rce.php';
Esto creará el fichero /tmp/rce.php que puede ser llamado por medio de una vulnerabilidad LFI para que el código sea ejecutado en el servidor. Sin embargo, un atacante también podría intentar escribir un fichero en la carpeta /var/www/html/ para no tener que requerir de una vulnerabilidad LFI para la ejecución de código.
Tampoco está demás decir que, si la versión del sistema gestor de bases de datos se encuentra desactualizado o utiliza cuentas de usuarios con permisos poco restringidos, podrían existir más formas directas para la ejecución de código, tal como se muestra en los siguientes enlaces:
- Oracle SQL Command execution
- PostgreSQL Command execution (CVE-2019–9193)
- Executing OS Commands Through Oracle
Conclusión
Como conclusión, la vulnerabilidad de inyección SQL es antigua y poco común hoy en día, debido a las adecuadas implementaciones de entornos de desarrollo y buenas prácticas de hardening. Sin embargo, si se encuentra presente, podría exponer información confidencial de los usuarios de una organización, secuestrar sus credenciales e incluso constituir una oportunidad para ejecutar código remoto en el servidor y obtener acceso a él.