Muchos desarrolladores web no son conscientes de cómo
pueden manipularse las consultas SQL, y asumen que una consulta
SQL es un comando confiable. Esto representa que las consultas
SQL pueden burlar los controles de acceso, y de este modo evitar
los chequeos estándares de autenticación y
autorización, y a veces las consultas SQL pueden incluso
permitir acceso a comandos al nivel del sistema operativo de la
máquina huésped.
La Inyección Directa de Comandos SQL es una técnica
en la cual un atacante crea o altera comandos SQL existentes para
exponer datos escondidos, o sobrescribir datos crÃticos, o
incluso ejecutar comandos del sistema peligrosos en la
máquina en donde se encuentra la base de datos. Esto se
consigue cuando la aplicación toma información de
entrada del usuario y la combina con parámetros
estáticos para construir una consulta SQL. Los siguientes
ejemplos, desafortunadamente, están basados en historias
reales.
Debido a la falta de validación de la información
de entrada y el establecimiento de conexiones con la base de
datos desde un super-usuario o aquel que puede crear usuarios, el
atacante podrÃa crear un super-usuario en su base de
datos.
Ejemplo 27-2.
Paginación del conjunto de resultados ... y
creación de super-usuarios (PostgreSQL)
|
<?php
$offset = $argv[0]; $consulta = "SELECT id, nombre FROM productos ORDER BY nombre LIMIT 20 " .
"OFFSET $offset;";
$resultado = pg_query($conexion, $consulta);
?>
|
|
Los usuarios normales pulsan sobre los enlaces 'siguiente' y
'anterior', en donde el desplazamiento
(
$offset) se encuentra codificado en la
URL. El script espera que el valor
entrante
$offset sea un número decimal.
Sin embargo, qué sucede si alguien intenta un ataque
añadiendo una forma codificada
(
urlencode()) de lo siguiente en la URL
Si esto ocurriera, entonces el script le presentarÃa un
acceso de superusuario al atacante. Note
que
0; es usado para ofrecer un desplazamiento
válido a la consulta original y finalizarla.
Nota:
Es una técnica común obligar al analizador
sintáctico de SQL a que ignore el resto de la consulta
escrita por el desarrollador mediante
--, el cual es el signo de comentarios en
SQL.
Una forma viable de adquirir contraseñas es jugar con las
páginas de resultados de búsquedas. Lo único
que necesita el atacante es ver si existen variables enviadas por
el usuario que sean usadas en sentencias SQL, y que no sean
tratadas apropiadamente. Estos filtros pueden ubicarse por lo
general previos a cláusulas WHERE, ORDER BY,
LIMIT y OFFSET en sentencias
SELECT para personalizar la
instrucción. Si su base de datos soporta la
construcción UNION, el atacante puede
intentar añadir una consulta completa a la consulta
original para generar una lista de contraseñas desde una
tabla cualquiera. El uso de campos encriptados de
contraseñas es altamente recomendable.
Ejemplo 27-3.
Listado de artÃculos ... y algunas contraseñas
(en cualquier base de datos)
|
<?php
$consulta = "SELECT id, nombre, insertado, tam FROM productos
WHERE tam = '$tam'
ORDER BY $orden LIMIT $limite, $offset;";
$resultado = odbc_exec($conexion, $consulta);
?>
|
|
La parte estática de la consulta puede combinarse con otra
sentencia
SELECT la cual revela todas las
contraseñas:
Si esta consulta (la cual juega con
' y
--) fuera asignada a una de las variables
usadas en
$consulta, la bestia de la consulta
habrá despertado.
Las sentencias UPDATE de SQL son también susceptibles a
ataque. Éstas consultas también se encuentran
amenazadas por un posible acotamiento y adición de una
consulta completamente nueva. Pero en este caso el atacante puede
amañar la información de una
cláusula SET. En este caso se requiere
contar con cierta información sobre el esquema de la base
de datos para poder manipular la consulta
satisfactoriamente. Esta información puede ser adquirida
mediante el estudio de los nombres de variables de los
formularios, o simplemente por fuerza bruta. No existen
demasiadas convenciones para nombrar campos de contraseñas
o nombres de usuario.
Ejemplo 27-4.
De restablecer una contraseña ... a adquirir más
privilegios (con cualquier servidor de base de datos)
|
<?php
$consulta = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
|
|
Pero un usuario malicioso envÃa el valor
' or uid
like'%admin%'; -- como
$uid para
cambiar la contraseña del administrador, o simplemente
establece
$pwd a
"hehehe', admin='yes',
trusted=100 " (con un espacio al inicio) para adquirir
más privilegios. En tal caso, la consulta serÃa
manipulada:
Un horrible ejemplo de cómo puede accederse a comandos del
nivel del sistema operativo en algunas máquinas
anfitrionas de bases de datos.
Ejemplo 27-5. Ataque al sistema operativo de la máquina
anfitriona de la base de datos (MSSQL Server) |
<?php
$consulta = "SELECT * FROM productos WHERE id LIKE '%$prod%'";
$resultado = mssql_query($consulta);
?>
|
|
Si el atacante envÃa el valor
a%' exec
master..xp_cmdshell 'net user test testpass /ADD' -- a
$prod, entones la
$consulta
será:
MSSQL Server ejecuta sentencias SQL en el lote, incluyendo un
comando para agregar un nuevo usuario a la base de datos de
cuentas locales. Si esta aplicación estuviera corriendo
como
sa y el servicio MSSQLSERVER está
corriendo con los privilegios suficientes, el atacante
tendrÃa ahora una cuenta con la que puede acceder a esta
máquina.
Nota:
Algunos de los ejemplos anteriores están atados a un
servidor de base de datos especÃfico. Esto no quiere
decir que un ataque similar sea imposible con otros
productos. Su base de datos puede ser vulnerable de forma
semejante, en alguna otra manera.
Usted puede argumentar con justa razón que el atacante
debe poseer cierta cantidad de información sobre el
esquema de la base de datos en la mayorÃa de ejemplos que
hemos visto. Tiene razón, pero usted nunca sabe
cuándo y cómo puede filtrarse esta
información, y si ocurre, su base de datos estará
expuesta. Si está usando un paquete de gestión de
bases de datos de código abierto, o cuyo código
fuente está disponible públicamente, el cual puede
pertenecer a algún sistema de administración de
contenido o foro, los intrusos pueden producir fácilmente
una copia de un trozo de su código. También puede
ser un riesgo de seguridad si es un segmento de código
pobremente diseñado.
Estos ataques se basan principalmente en la explotación
del código que no ha sido escrito pensando en la
seguridad. Nunca confÃe en ningún tipo de
información de entrada, especialmente aquella que
proviene del lado del cliente, aun si lo hace desde una caja de
selección, un campo de entrada hidden o una cookie. El
primer ejemplo le muestra que una consulta asà de
descuidada puede causar desastres.
Nunca se conecte a la base de datos como un super-usuario o
como el dueño de la base de datos. Use siempre usuarios
personalizados con privilegios muy limitados.
Revise si la entrada recibida es del tipo apropiado. PHP posee
un amplio rango de funciones de validación de datos,
desde los más simples encontrados en Funciones sobre variables y
en Funciones de tipo de
caracter (p. ej. is_numeric(),
ctype_digit() respectivamente) hasta el
soporte para Expresiones Regulares
compatibles con Perl.
Si la aplicación espera alguna entrada numérica,
considere la verificación de información
con is_numeric(), o modifique
silenciosamente su tipo usando settype(), o
utilice su representación numérica, dada por
sprintf().
Ejemplo 27-6.
Una forma más segura de generar una consulta para
paginado
|
<?php
settype($offset, 'integer');
$consulta = "SELECT id, nombre FROM productos ORDER BY nombre " .
"LIMIT 20 OFFSET $offset;";
$consulta = sprintf("SELECT id, nombre FROM productos ORDER BY nombre" .
"LIMIT 20 OFFSET %d;", $offset);
?>
|
|
Ubique cada valor no-numérico que entrega el usuario y
que sea pasado a la base de datos entre comillas con la
función de escape de cadenas especÃfica a su
base de datos (p.ej.
mysql_escape_string(),
sql_escape_string(), etc.). Si no hay
disponible un mecanismo de escape de cadenas especÃfico
a la BD, las funciones addslashes() y
str_replace() pueden ser útiles
(dependiendo del tipo de base de datos). Vea el primer
ejemplo. Como se ve allÃ, agregar comillas a la
parte estática de la consulta no es suficiente,
haciéndola fácilmente manipulable.
No imprima ninguna información especÃfica sobre
la base de datos, especialmente sobre su esquema, ya sea por
razones justas o por equivocaciones. Vea también Reporte de Errores y Gestión de Errores y Funciones
de Registro.
Puede usar procedimientos almacenados y cursores previamente
definidos para abstraer el acceso a las bases de datos, de
modo que los usuarios no tengan acceso directo a las tablas o
vistas, aunque esta solución tiene otros impactos.
Además de estas acciones, usted puede beneficiarse del
registro explÃcito de las consultas realizadas, ya sea
desde su script o por la base de datos misma, si ésta
soporta la gestión de registros. Por supuesto, el
registro de acciones no puede prevenir cualquier intento
peligroso, pero puede ser útil para rastrear
cuáles aplicaciones han sido usadas para violar la
seguridad. El registro en sà no es útil; lo es la
información que contiene. Por lo general, es mejor contar
con más detalles que con menos.