Inyección de SQL
Muchos desarrolladores web son desprevenidos de cómo las consultas SQL pueden ser manipuladas,
y asumen que una consulta SQL es un comando confiable. Esto significa que las consultas SQL
están expuestas a que sean malversadas en controles de acceso, sobrepasando así las
revisiones de autenticación y autorización estándar, y que algunas veces las consultas SQL aún
podrían permitir el acceso de comandos a nivel de sistema operativo del ordenador.
Comandos directos de Inyección SQL es una técnica donde un atacante crea o
altera comandos SQL existentes para exponer datos ocultos, sobreponerse a los que
son importantes, o peor aún, ejecutar comandos peligrosos a nivel de sistema en el equipo
donde se encuentra la base de datos. Esto se logra a través de la aplicación, tomando la entrada del usuario y
combinándola con parámetros estáticos para elaborar una consulta SQL. Los siguientes ejemplos
están basados en historias reales, desafortunadamente.
Debido a la falta de validación en la entrada de datos y conectándose a la base de datos
con privilegios de super usuario o de alguien con privilegios que puede crear usuarios, el atacante
podría crear un super usuario en su base de datos.
Ejemplo #1 Dividiendo el conjunto de resultados en páginas ... y haciendo super usuarios (PostgreSQL)
<?php
$offset = $argv[0]; // Cuidado, no hay validación en la entrada de datos!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
?>
Los usuarios normales dan clic en los enlaces 'siguiente' o 'atras' donde
$offset
está codificado en la
URL. El script espera que el
$offset entrante sea un número décimal. Sin embargo, qué pasa si alguien intenta
irrumpir añadiendo una función
urlencode() al formulario de la
siguiente
URL
Si esto sucedió, entonces el script podría presentarle un acceso de super usuario al atacante.
Nótese que
0; es para proveer un offset válido a la consulta original
y para finalizarla.
Nota:
Esta es una técnica común para forzar al analizador SQL a que ignore el resto de la
consulta escrita por el desarrollador con dos guiónes: -- los cuales
representan 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 usadas en declaraciones SQL las cuales no sean manejadas apropiadamente. Esos filtros pueden ser puestos
comunmente en un formulario anterior para personalizar las cláusulas WHERE, ORDER BY,
LIMIT y OFFSET en las declaraciones SELECT.
Si su base de datos soporta el constructor UNION,
el atacante podría intentar añadir una consulta enetera a la consulta original para listar
contraseñas de una tabla arbitraria. Utilizar campos de contraseña encriptadoslds es fuertemente
recomendado.
Ejemplo #2
Listando nuestros artículos ... y algunas contraseñas (de cualquier servidor de base de datos)
<?php
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'";
$result = odbc_exec($conn, $query);
?>
La parte estática de la consulta puede ser combinada con otra declaración
SELECT la cual revela todas las contraseñas:
Si esta consulta (ejecutándose con
' y
--) fuera asignada a una de las variables utilizadas en
$query, la consulta reaccionará bestialmente.
Las consultas de actualización de SQL, también son susceptibles a ataques. Estas consultas también
son amenazadas por acortamiento y adición en una consulta completamente nueva a esta. Sin embargo
el atacante podría manipularla con la cláusula SET. En este caso, algunos
esquemas de información deben ser procesados para manipular la consulta exitosamente.
Este puede adquirirse examinando la forma de nombres de las variables, o simplemente forzarlo
con un ataque de fuerza bruta. No hay muchas convenciones de nombres para campos que almacenan
contraseñas o nombres de usuarios.
Ejemplo #3
Desde re-establecer una contraseña ... hasta ganar más privilegios (en cualquier servidor de bases de datos)
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
Pero un usuario malicioso podría enviar el valor
' or uid like'%admin% a
$uid para
cambiar la contraseña del administrador, o simplemente cambiar
$pwd a
hehehe', trusted=100, admin='yes para obtener más
privilegios. Entonces, la consulta sería cambiada:
Un ejemplo horrible de cómo pueden ser accedidos los comandos a nivel de sistema operativo
en algunos hospedadores de bases de datos.
Ejemplo #4 Atacando el sistema operativo que hospeda la base de datos (Servidor MSSQL)
<?php
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
?>
Si un atacante envía el valor
a%' exec master..xp_cmdshell 'net user test testpass /ADD' --
hacia
$prod, la consulta
$query será:
El servidor MSSQL ejecuta la sentencia SQL en el lote que incluye un comando
para añadir un usuario nuevo a la base de datos de cuentas locales. Si esta aplicación
estuviera ejecutándose como
sa, y el servicio MSSQLSERVER se está
ejecutando con los privilegios suficientes, el atacante ahora podría tener una cuenta
con la cual tendría acceso a esta máquina.
Nota:
Algunos de los ejemplos de citados arriba estan vinculados a un servidor de base de datos específico. Esto
no significa que un ataque similar sea imposible en contra de otros productos.
Su servidor de base de datos podría ser vulnerable de forma similar en otra manera.
Imagen cortesía de
» xkcd
Técnicas de evitación
Pese a que pueda parecer obvio que un atacante debe tener al menos algún
conocimiento de arquitecturas de bases de datos para poder realizar un ataque
con éxito, el obtener esta información suele ser muy sencillo. Por ejemplo,
cuando la base de datos forma parte de un paquete de software libre, o disponible
públicamente, con una instalación predefinida, esta información se encuentra
completamente libre y disponible. Esta información puede haber sido divulgada
en proyectos de código cerrado - incluso si está codificad, ofuscada o compilada -
incluso por el propio código mediante mensajes de error.
Otros métodos incluyen el uso de nombres de tablas y columnas frecuentes. Por ejemplo,
un formulario de inicio de sesión que utiliza una tabla 'users' con los nombres
de columna 'id', 'username', y 'password'.
Esos ataques están principalmente basados en explotar el código que no ha sido escrito
teniendo en mente la seguridad. Nunca confíes en ningún tipo de entrada, especialmente la que
viene del lado del cliente, aún cuando esta venga de una caja de selección,
un campo oculto o una cookie. El primer ejemplo muestra que una inofensiva consulta
puede causar desastres.
-
Nunca se conecte como super usuario o como el propietario de la base de datos.
Siempre utilice usuarios personalizados con privilegios muy limitados.
-
Use sentencias preparadas con variables vinculadas. Son proporcionadas por
PDO,
MySQLi
y otras bibliotecas.
-
Revise si la entrada proporcionada tiene el tipo de datos que se espera. PHP tiene
un rango amplio de funciones para validar la entrada de datos, desde las más simples
encontradas en Funciones de variable y en
Funciones de tipo Caracter
(Ej. is_numeric(), ctype_digit()
respectivamente) y siguiendo el apoyo con las
Expresiones regulares compatibles con Perl.
-
Si la expresión espera una entrada numérica, considere verificar los datos
con la función ctype_digit(), o silenciosamente cambie su tipo
utilizando settype(), o use su representación numérica
por medio de sprintf().
Ejemplo #5 Una forma más segura de redactar una consulta para paginación
<?php
settype($offset, 'integer');
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
// Fíjese en %d en el formato de cadena, utilizar %s podría no tener un resultado significativo
$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
$offset);
?>
-
Si la capa de la base de datos no admite variables vinculadas,
entrecomille cada valor no numérico proporcionado por el usuario que sea pasado a la
base de datos con la función de escapado de cadenas de caracteres específica de la base de datos (p.ej.
mysql_real_escape_string(),
sqlite_escape_string(), etc.).
Las funciones genéricas como addslashes() son útiles solamente
en un entorno muy específico (p.ej., MySQL en un conjunto de caracteres monobyte
con NO_BACKSLASH_ESCAPES deshabilitada), por lo que es
mejor evitarlas.
-
No muestre ninguna información específica de la base de datos, especialmente
sobre el esquema, por su correcto significado es como jugar sucio contra usted mismo. Vea también Reporte de errores y Manejo de errores y funciones de registro.
-
Podría utilizar procedimientos almacenados y previamente cursores definidos, para abstraer
el acceso a datos para que los usuarios no tengan acceso directo a las tablas o vistas, para
que esta solución tenga otros impactos.
Junto a esto, usted se beneficia de tener un registro de las consultas ya sea dentro de su script
o de la base de datos en si misma, si es que esta soporta el registro. Obviamente, llevar un registro
no le previene cualquier intento de daño, pero éste puede ser útil para hacer una retro revisión de cual
aplicación ha sido intervenida. El registro no es útil por sí mismo, pero lo es debido a la información
que contiene. Más detalles generalmente es mejor que los pocos.