La inyección SQL es una técnica en la que un atacante aprovecha fallas en el código de la aplicación encargado de construir consultas SQL dinámicas. El atacante puede obtener acceso a secciones privilegiadas de la aplicación, recuperar toda la información de la base de datos, manipular datos existentes o incluso ejecutar comandos peligrosos a nivel del sistema en el host de la base de datos. La vulnerabilidad ocurre cuando los desarrolladores concatenan o interpolan entrada arbitraria en sus declaraciones SQL.
Ejemplo #1 Dividir el conjunto de resultados en páginas ... y crear superusuarios (PostgreSQL)
En el siguiente ejemplo, la entrada del usuario se interpola directamente en la consulta SQL, lo que permite al atacante obtener una cuenta de superusuario en la base de datos.
<?php
$índice = $_GET['offset']; // ¡Cuidado, no hay validación en la entrada de datos!
$consulta = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $índice;";
$resultado = pg_query($conexión, $consulta);
?>
0; insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) select 'crack', usesysid, 't','t','crack' from pg_shadow where usename='postgres'; --
0;
es para proveer un índcie válido a la consulta original y así finalizarla. Nota:
Es una técnica común forzar al analizador SQL a ignorar el resto de la consulta escrita por el desarrollador con
--
, lo cual representa un comentario en SQL.
Una forma factible de obtener contraseñas es burlar las páginas de búsqueda de resultados. Lo único que el atacante necesita hacer es ver si hay variables que hayan sido enviadas y sean empleadas en sentencias SQL que no sean manejadas apropiadamente. Estos filtros se pueden establecer comunmente en un formulario anterior para personalizar las cláusulas WHERE, ORDER BY, LIMIT
y OFFSET
en las sentencias SELECT
. Si la base de datos admite el constructor UNION
, el atacante podría intentar anteponer una consulta entera a la consulta original para enumerar las contraseñas de una tabla arbitraria. Se recomienda encarecidamente almacenar solo hashes seguros de contraseñas en lugar de las contraseñas mismas.
Ejemplo #2 Enumerar artículos ... y algunas contraseñas (cualquier servidor de bases de datos)
<?php
$consulta = "SELECT id, name, inserted, size FROM products
WHERE size = '$tamaño'";
$resultado = odbc_exec($conexión, $consulta);
?>
SELECT
la cual revelará todas las contraseñas: ' union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable; --
Las declaraciones UPDATE
e INSERT
también son susceptibles a este tipo de ataques.
Ejemplo #3 Desde restablecer una contraseña ... hasta obtener más privilegios (cualquier servidor de bases de datos)
<?php
$consulta = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
' or uid like'%admin%
a $uid para cambiar la contraseña del administrador, o simplemente cambiar $pwd a jejeje', trusted=100, admin='yes
para obtener más privilegios, entonces la consulta se tornaría: <?php
// $uid: ' or uid like '%admin%
$consulta = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";
// $pwd: jejeje', trusted=100, admin='yes
$consulta = "UPDATE usertable SET pwd='jejeje', trusted=100, admin='yes' WHERE
...;";
?>
Aunque sigue siendo evidente que un atacante debe poseer al menos cierto conocimiento de la arquitectura de la base de datos para llevar a cabo un ataque exitoso, obtener esta información a menudo es muy simple. Por ejemplo, el código puede ser parte de un software de código abierto y estar disponible públicamente. Esta información también puede ser revelada por código fuente cerrado, incluso si está codificado, ofuscado o compilado, e incluso por tu propio código a través de la visualización de mensajes de error. Otros métodos incluyen el uso de nombres de tablas y columnas típicos. Por ejemplo, un formulario de inicio de sesión que utiliza una tabla 'users' con nombres de columnas 'id', 'username' y 'password'.
Ejemplo #4 Atacar el sistema operativo del host de la base de datos (MSSQL Server)
Un ejemplo alarmante de cómo los comandos a nivel del sistema operativo pueden ser accedidos en algunos hosts de bases de datos.
<?php
$consulta = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$resultado = mssql_query($consulta);
?>
a%' exec master..xp_cmdshell 'net user test testpass /ADD' --
a $prod, la $consulta será: <?php
$consulta = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD' --%'";
$resultado = mssql_query($consulta);
?>
sa
y el servicio MSSQLSERVER estaba ejecutando con los privilegios suficientes, el atacante ahora podría tener una cuenta con la cual acceder a esta máquina. Nota:
Algunos ejemplos anteriores están vinculados a un servidor de bases de datos específico, pero esto no significa que un ataque similar sea imposible contra otros productos. Tu servidor de bases de datos puede ser igualmente vulnerable de otra manera.
La forma recomendada de evitar la inyección SQL es vincular todos los datos mediante declaraciones preparadas. Utilizar consultas parametrizadas no es suficiente para evitar por completo la inyección SQL, pero es la manera más fácil y segura de proporcionar datos a las declaraciones SQL. Todas las literales de datos dinámicos en las cláusulas WHERE
, SET
, y VALUES
deben ser reemplazadas con marcadores de posición. Los datos reales se vincularán durante la ejecución y se enviarán por separado del comando SQL.
La vinculación de parámetros solo puede utilizarse para datos. Otras partes dinámicas de la consulta SQL deben filtrarse contra una lista conocida de valores permitidos.
Ejemplo #5 Evitar la inyección SQL mediante el uso de declaraciones preparadas con PDO
<?php
// La parte dinámica del SQL se valida contra valores esperados
$sortingOrder = $_GET['sortingOrder'] === 'DESC' ? 'DESC' : 'ASC';
$productId = $_GET['productId'];
// El SQL se prepara con un marcador de posición
$stmt = $pdo->prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}");
// El valor se proporciona con comodines LIKE
$stmt->execute(["%{$productId}%"]);
?>
Las declaraciones preparadas son proporcionadas por PDO, MySQLi y otras bibliotecas de bases de datos.
Los ataques de inyección SQL se basan principalmente en explotar código que no ha sido escrito teniendo en cuenta la seguridad. Nunca confíes en ninguna entrada, especialmente desde el lado del cliente, incluso si proviene de un cuadro de selección, un campo de entrada oculto o una cookie. El primer ejemplo muestra que una consulta tan simple puede causar desastres.
Una estrategia de defensa en profundidad implica varias buenas prácticas de codificación:
Además, se puede beneficiar del registro de consultas, ya sea dentro de un script o mediante la base de datos en sí misma, si es que lo soporta. Obviamente, llevar un registro no previene los intentos dañinos, aunque puede ser útil para realizar un seguimiento de las aplicación que han sido eludidas. El registro no es útil por sí mismo sino por la información que contiene. Normalmente cuantos más detalles, mejor.