Cómo prevenir un ataque de inyección de SQL
30/03/2022
Por Graciela Martínez y Guillermo Pereyra – LACNIC CSIRT
El ataque de inyección de SQL es una técnica de hackeo web muy utilizada. Conocido como SQLi, es un ataque donde se “inyecta” una o varias sentencias SQL válidas a una consulta de una aplicación a una base de datos.
Structured Query Language, conocido por sus siglas en inglés como SQL, es un lenguaje de comando y control para el manejo de base de datos relacionales.
¿Cómo se logra un ataque de inyección SQL?
El ataque se logra cuando una aplicación acepta datos de fuentes no confiables- que fueron alterados para ser interpretados como código- y además no realiza una correcta validación de los mismos antes de utilizarlos para realizar una consulta dinámica a la base de datos.
Vectores de ataque
A la hora de diseñar una aplicación es importante tener en cuenta que cualquier entrada de datos por parte de un usuario es susceptible a ser modificada de forma arbitraria.
Habitualmente, los ataques sobre aplicaciones web son llevados a cabo mediante la modificación de parámetros en la URL, utilizando el método HTTP GET o, como veremos en los ejemplos, modificando cualquier tipo de entrada de datos con el método HTTP POST. Según cómo esté diseñada la aplicación web, es posible explotar esta vulnerabilidad ya sea modificando cabeceras HTTP como User Agent, cookies, referrer o cabeceras propias del sistema.
Se deben considerar otros posibles vectores de ataque tales como lector de barras, lector QR, o cámara de vídeo que reconozca texto.
Es importante tener en cuenta que un atacante puede ser externo o pertenecer a la organización afectada.
¿Cuáles son las posibles consecuencias de un ataque de inyección SQL?
Cualquier acción no deseada que se ejecute sobre una base de datos como consecuencia de un ataque por SQLi puede afectar uno o varios pilares de seguridad de la información.
Un ataque exitoso puede permitir al atacante concretar diversas acciones que afecten la confidencialidad, integridad y/o disponibilidad de la información.
Ejemplos:
- La confidencialidad puede verse afectada en caso de que ocurra un acceso a información sensible sin contar con la autorización necesaria.
- La Integridad puede verse afectada si se elimina o se modifica información sin contar con la autorización necesaria.
- La disponibilidad puede verse afectada si la información no está disponible en el momento que se precise, ya sea porque no se puede acceder a ella o porque la misma haya sido alterada en forma previa sin autorización.
Otros problemas que podría explotar un ataque de SQLi son fallas en la autenticación y/o modificación de la autorización que tiene un perfil de usuario para realizar determinadas acciones sobre un recurso. Por ejemplo, si se accediera a la información de una base de datos donde se almacenan claves de usuarios, si las mismas están ofuscadas con hashes criptográficos robustos se evitaría el acceso indebido a ellas y su posible utilización por parte de un atacante.
Por lo descrito, un ataque exitoso de SQLi afecta varios pilares a la vez.
¿Cómo prevenir un ataque de inyección SQL?
Existen distintos tipos de medidas que se deben considerar cuando se necesita comunicar un sistema con una base de datos relacional a través del uso de SQL.
A continuación se mencionan algunas medidas básicas que se deberían implementar:
- Uso de consultas parametrizadas: Prepared Statements.
- Uso de procedimientos: Stored Procedures.
- Validación de entrada de datos de usuarios
- Escapar todas las entradas permitidas de los usuarios.
Uso de consultas parametrizadas: Prepared Statements.
Preparar las declaraciones sql y almacenarlas en una variable antes de la ejecución es una forma simple y segura de programar. Al hacerlo de forma anticipada se evita que un atacante inserte declaraciones en nuestra base de datos, como se puede ver en el segundo ejemplo.
Los lenguajes más usados poseen métodos para parametrizar de manera segura las declaraciones que precisan datos de entrada de un usuario. Alguno de estos lenguajes son:
- Java EE – usar PreparedStatement() en nuestros parámetros dinámicos (variable Bind en inglés)
- .NET – usar consultas parametrizadas como SqlCommand() o OleDbCommand() en nuestros parámetros dinámicos.
- PHP – es posible usar PDO para base de datos genéricas con una fuerte parametrización de las consultas o en caso de usar un driver específico para una base de datos es necesario buscar una función segura para preparar nuestra declaración, por ejemplo, para MySQL es necesario usar bind_param().
Uso de procedimientos: Stored Procedures.
Al ejecutar procedimientos directamente en la base de datos es necesario tener el cuidado de no incluir ninguna generación de declaración SQL dinámica no segura. Estos procedimientos deben tener validación de las entradas y un adecuado “escape” de las mismas.
Validación de entrada de datos de usuarios
En todos los casos, y no solo para prevenir SQL injections, es necesario la correcta validación de la entrada de datos por parte de los usuarios.
No es recomendable que se usen variables dinámicas para nombres de tablas o columnas, así como tampoco para indicar el orden de clasificación (ASC o DESC). En caso de necesitarlas se deben usar validaciones previas como convertir las entradas a variables booleanas o usar funciones SWITCH, Sort of, etc.
Escapar todas las entradas permitidas de los usuarios: Escape characters.
Escapar entradas de usuarios para convertirlas en otro formato como strings, debe ser usado con cuidado ya que no evita todas las inyecciones. Esta técnica escape characters depende de cada motor de base de datos por lo que es importante implementar un control que impida un posible bypass de esta medida.
Ejemplos
A continuación dejamos algunos ejemplos de códigos que permitirían ataques de SQLi.
Ejemplo 1: Separar resultados en páginas, usando PHP y Postgres.
<?php
$índice = $argv[0];
$consulta = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $índice;";
$resultado = pg_query($conexión, $consulta);
?>
Un usuario normalmente utiliza los botones “siguiente” y “atrás” para navegar entre los resultados, lo cual modificaría el valor decimal de la variable “índice” en la URL.
Este comportamiento no generaría problemas, pero si un agente malicioso decide agregar el siguiente código en la URL:
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'crack', usesysid, 't','t','crack'
from pg_shadow where usename='postgres';
--
Entonces la variable $consulta quedaría de la siguiente manera:
$consulta = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET 0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'superusuario', usesysid, 't','t','password'
from pg_shadow where usename='postgres';
--;"
y como resultado estaría creando el usuario “superusuario” con privilegios, por lo tanto quedaría habilitado para llevar a cabo actividades maliciosas como las que se describieron anteriormente en el artículo.
Una forma de corregir este problema sería el uso de PDO (PHP Data Objects):
<?php
$stmt = $pdo->prepare("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET :índice;");
$índice = $argv[0];
$stmt->bindParam(':indice', $indice);
$stmt->execute()
?>
Ejemplo 2: Bypass de autenticación
Supongamos una aplicación web que tiene un formulario de autenticación que acepta como entradas un usuario y una contraseña.
Este formulario está siendo procesado por un código que contiene la siguiente declaración SQL:
consulta = "SELECT * FROM users WHERE username = "'" + username + "' AND password = '" + password + "'"
Como se puede apreciar la consulta a la base de datos está construída mediante el uso de sentencias SQL y se asignan a las variables de forma directa los valores introducidos por el usuario.
En este caso un usuario malintencionado podría ingresar como usuario:
admin y como contraseña pass’ OR ‘1’=’1.
La consulta a la base de datos final sería la siguiente:
consulta = SELECT * FROM users WHERE username = 'admin' AND (password = ' pass' OR '1'='1 ')
Esta consulta traería todos los datos asociados al usuario privilegiado ‘admin’ porque la condición booleana siempre será verdadera.
Conclusión
Un ataque de inyección de SQL es evitable si se implementan los controles necesarios durante el desarrollo de las aplicaciones.
Los equipos de desarrolladores deben de contar con un procedimiento que contemple las buenas prácticas para un desarrollo seguro de software, que incluya un tiempo prudente para la etapa de testing antes de su pasaje a producción.
Referencias:
https://blog.sucuri.net/2022/01/understanding-website-sql-injections.html
https://owasp.org/Top10/A03_2021-Injection/
https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
https://www.esecurityplanet.com/threats/how-to-prevent-sql-injection-attacks/
https://www.php.net/manual/es/security.database.sql-injection.php
https://www.esecurityplanet.com/threats/how-to-prevent-sql-injection-attacks/
[…] web.14 Usar PDO (PHP Data Objects) para conectarse a la base de datos es crucial para evitarla.15 PDO emplea sentencias ya preparadas para que los datos del usuario no se confundan con el lenguaje […]