Hoy en día, las bases de datos son componentes cardinales
de cualquier aplicación basada en web, permitiendo que los
sitios web provean contenido dinámico. Debido a que
información considerablemente sensible o secreta puede ser
almacenada en una base de datos, usted debe considerar seriamente
la protección de sus bases de datos.
Para recuperar o almacenar cualquier información necesita
conectarse a la base de datos, enviar una consulta válida,
recoger el resultado y cerrar la conexión. Hoy en
día, el lenguaje de consultas usado comúnmente en
estas interacciones es el Lenguaje de Consultas Estructurado (SQL
por sus siglas en Inglés). Puede apreciar cómo un
atacante puede
intentar
acometidas con una consulta SQL.
Como puede suponer, PHP no puede proteger su base de datos por
sí solo. Las siguientes secciones están dirigidas a
servir de introducción a los conceptos básicos de
cómo acceder y manipular bases de datos desde scripts PHP.
Mantenga en mente esta simple regla: protección en
profundidad. Entre más acciones tome para incrementar la
protección de su base de datos, menor será la
probabilidad de que un atacante tenga éxito exponiendo o
abusando de cualquier información almacenada. Un buen
diseño del esquema de la base de datos y de la
aplicación basta para lidiar con sus mayores temores.
El primer paso siempre es crear la base de datos, a menos que
desee usar una creada por alguien más. Cuando una base de
datos es creada, ésta es asignada a un dueño,
quien ejecutó la sentencia de
creación. Usualmente, únicamente el dueño
(o un super-usuario) puede hacer cualquier cosa con los objetos
de esa base de datos, y para que otros usuarios puedan usarla,
deben otorgarse privilegios.
Las aplicaciones nunca deberían conectarse a la base de
datos bajo el usuario correspondiente a su dueño, o como
un super-usuario, ya que éstos usuarios pueden, por
ejemplo, ejecutar cualquier consulta a su antojo, modificando el
esquema (p. ej. eliminando tablas) o borrando su contenido
completo.
Usted puede crear diferentes usuarios de la base de datos para
cada aspecto de su aplicación con derechos muy limitados
sobre los objetos de la base de datos. Tan solo deben otorgarse
los privilegios estrictamente necesarios, y evitar que el mismo
usuario pueda interactuar con la base de datos en diferentes
casos de uso. Esto quiere decir que si un intruso gana acceso a
su base de datos usando las credenciales de sus aplicaciones,
él solo puede efectuar tantos cambios como su
aplicación se lo permita.
Es buena idea que no implemente toda la lógica del asunto
en la aplicación web (es decir, en su script); en su
lugar, hágalo en el esquema de la base de datos usando
vistas, disparadores o reglas. Si el sistema evoluciona, se
espera que nuevos puertos sean abiertos a la aplicación,
y tendrá que re-implementar la lógica para cada
cliente de la base de datos. Por sobre todo, los disparadores
pueden ser usados para gestionar de forma transparente todos los
campos automáticamente, lo cual con frecuencia provee
información útil cuando se depuren problemas de su
aplicación, o se realicen rastreos sobre transacciones
particulares.
Puede que desee establecer las conexiones sobre SSL para
encriptar las comunicaciones cliente/servidor, incrementando el
nivel de seguridad, o puede hacer uso de ssh para encriptar la
conexión de red entre los clientes y el servidor de la
base de datos. Si cualquiera de estos recursos es usado, entonces
monitorear su tráfico y adquirir información sobre
su base de datos será difícil para un atacante
potencial.
SSL/SSH protege los datos que viajan desde el cliente al
servidor, SSL/SSH no protege los datos persistentes almacenados
en la base de datos. SSL es un protocolo sobre-el-cable.
Una vez el atacante adquiere acceso directo a su base de datos
(evitando el paso por el servidor web), los datos críticos
almacenados pueden estar expuestos o malutilizados, a menos que
la información esté protegida en la base de datos
misma. La encriptación de datos es una buena forma de
mitigar esta amenaza, pero muy pocas bases de datos ofrecen este
tipo de mecanismo de encriptación de datos.
La forma más sencilla de evitar este problema es crear
primero su propio paquete de encriptación, y luego
utilizarlo desde sus scripts de PHP. PHP puede ayudarle en este
sentido con varias extensiones, como Mcrypt y Mhash, las cuales cubren una amplia
variedad de algoritmos de encriptación. El script encripta
los datos antes de insertarlos en la base de datos, y los
decripta cuando los recupera. Vea las referencias para consultar
más ejemplos de cómo opera la encriptación.
En el caso de datos realmente escondidos, si su
representación original no se necesita (es decir, no debe
ser desplegada), los resúmenes criptográficos
pueden llegar a considerarse también. El ejemplo
clásico de gestión de resúmenes
criptográficos es el almacenamiento de secuencias MD5 de
una contraseña en una base de datos, en lugar de la
contraseña misma. Vea
también crypt()
y md5().
Ejemplo 16-5. Uso de un campo de contraseñas encriptado
<?php
// almacenamiento de resumen criptografico de la contrasenya
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 16-6.
Paginación del conjunto de resultados ... y
creación de super-usuarios (PostgreSQL y MySQL)
<?php
$offset = argv[0]; // atencion, no se valida la entrada! $consulta = "SELECT id, nombre FROM productos ORDER BY nombre LIMIT 20 " . "OFFSET $offset;";
// con PostgreSQL $resultado = pg_exec($conexion, $consulta);
// con MySQL $resultado = mysql_query($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
// en el caso de PostgreSQL
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'crack', usesysid, 't','t','crack'
from pg_shadow where usename='postgres';
--
// en el caso de MySQL
0;
UPDATE user SET Password=PASSWORD('crack') WHERE user='root';
FLUSH PRIVILEGES;
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 16-7.
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:
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--
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 16-8.
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:
<?php
// $uid == ' or uid like'%admin%'; -- $consulta = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --";
// $pwd == "hehehe', admin='yes', trusted=100 " $consulta = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE ...;"
?>
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 16-9. 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á:
<?php
$consulta = "SELECT * FROM productos WHERE id LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--"; $resultado = mssql_query($consulta);
?>
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.
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 16-10.
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;";
// note el simbolo %d en la cadena de formato, usar %s no tendria sentido $consulta = sprintf("SELECT id, nombre FROM productos ORDER BY nombre" . "LIMIT 20 OFFSET %d;", $offset);
?>
Ubique cada entrada del usuario no-numérica que sea
pasada a la base de datos entre comillas
con addslashes() o
addcslashes(). Vea el primer
ejemplo. Como se ve allí, las comillas colocadas
en la parte estática de la consulta no son suficientes,
y pueden ser manipuladas fácilmente.
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.