Seguridad de Bases de Datos

Hoy en día, las bases de datos son componentes cardinales de cualquier aplicación basada en la web permitiendo a los sitios web que provean una variedad de contenido dinámico. Esta información muy sensible o secreta puede ser almacenada en una base de datos, por lo que debe considerar fuertemente proteger su base de datos.

Para devolver o almacenar cualquier información usted necesita conectarse a la base de datos, enviar una consulta legítima, devolver el resultado, y cerrar la conexión. Hoy en día, el lenguaje de consultas comunmente utilizado en esta interacción es el Lenguaje Estructurado de Consultas (SQL, por sus siglas en inglés). Vea como un atacante puede entrometerse con una consulta maliciosa de SQL.

Como puede suponer, PHP no protege su base de datos por sí mismo. Las siguientes secciones piensan ser una introducción a lo más básico de cómo acceder y manipular base de datos dentro de scripts de PHP.

Tenga en mente esta simple regla: Protección en profundidad. En la mayoría de sitios tome la acción de incrementar la protección de su base de datos, para una menor probabilidad de que un atacante tenga éxito en exponer o abusar de cualquier información que tenga almacenada. El buen diseño del esquema de la base de datos y de la aplicación se ocupará de sus mayores temores.

Diseñando la base de datos

El primer paso es siempre crear una base de datos, a menos que quiera utilizar una de terceras personas. Cuando una base de datos es creada, ésta es asignada a un propietario, el que ha ejecutado la sentencia de creación. Usualmente, sólo el propietario (o un superusuario) puede hacer cualquier cosa con los objetos en esa base de datos, y para permitir a otros usuarios que puedan utilizarla, debe concederles privilegios.

Las aplicaciones nunca deberían conectarse a la base de datos como su propietario o como superusuario, porque estos usuarios pueden ejecutar cualquier consulta a su antojo, por ejemplo, modificar el esquema (Ej., eliminar tablas) o borrar su contenido entero.

Puede crear distintos usuarios de la base de datos para cada aspecto de su aplicación con permisos muy limitados a los objetos. La mayoría de privilegios que son requeridos deberían ser solamente otorgados, y así evitar que el mismo usuario pueda interactuar con la base de datos en diferentes casos y usos. Esto significa que si los intrusos ganan acceso a su base de datos utilizando las credenciales de la aplicación, solamente afecta a los cambios que su aplicación permita.

Usted está encarecido a no implementar toda la lógica del negocio en la aplicación web (Ej., sus scripts), en su lugar hágalo en el esquema de la base de datos utilizando vistas, disparadores o reglas. Si el sistema evoluciona, se pensará en abrir nuevos puertos a la base de datos, y usted tendrá que re-implementar la lógica en cada base de datos del cliente por separado. Al respecto de lo antes citado, los disparadores pueden ser utilizados para manerjar campos transparentes y automáticamente, lo cual a menudo provee un vistazo al interior cuando hay problemas de depuración con su programa o con el sistema de seguimientos de transacciones de su aplicación.

Conectándose a la base de datos

Puede ser que quiera establecer las conecciones sobre SSL para encriptar la comunicación cliente/servidor para incrementar la seguridad, o también puede usar ssh para encriptar la conexión de red entre los clientes y el servidor de base de datos. Si alguno de éstos es utilizado, el monitoreo de su tráfico y la obtención de información sobre su base de datos será difícil para un posible atacante.

Modelo de almacenamiento encriptado

SSL/SSH proteje los datos que viajan desde el cliente al servidor: SSL/SSH no proteje los datos persistentes almacenados en una base de datos. SSL es un protocolo para proteger los datos mientras viajan en el cable.

Una vez un atacante gana acceso directamente a su base de datos (sobre pasando el servidor web), los datos sencibles podrían ser divulgados o mal utilizados, a menos que la información esté protegida en la base de datos por sí misma. Encriptando los datos es una buena forma de mitigar esta amenaza, pero muy pocas bases de datos ofrecen este tipo de encripción de datos.

La forma más fácil para trabajar en este problema, es crear primero su propio paquete de encripción, y utilizarlo desde de sus scripts de PHP. PHP puede guiarle en esto con muchas extensiones, tales como Mcrypt y Mhash, cubriendo así, una amplia variedad de algoritmos de encripción. El script encripta los datos antes de insertarlos dentro de la base de datos, y los desencripta cuando los devuelve. Vea las referencias para ejemplos adicionales de como funciona la encripción.

En caso de datos que deben estar ocultos, si no fuera necesario usar su representación real (es decir, que no se mostrarán), quizás convenga utilizar hashing. El ejemplo más típico de hashing es cuando se almacena el hash MD5 de una contraseña en una base de datos, en lugar de almacenar la contraseña en sí misma. Vea también las funciones crypt() y md5().

Ejemplo #1 Utilizando campos de contraseña con hash

<?php

// Almacenando la contraseña con hash
$query  sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
            
pg_escape_string($username), md5($password));
$result pg_query($connection$query);

// Consultando si el usuario envió la contraseña correcta
$query sprintf("SELECT 1 FROM users WHERE name='%s' AND pwd='%s';",
            
pg_escape_string($username), md5($password));
$result pg_query($connection$query);

if (pg_num_rows($result) > 0) {
    echo 
'Bienvenido/a, $username!';
} else {
    echo 
'La autenticación ha fallado para $username.';
}

?>

Inyección de SQL

Muchos desarrolladores web son desprevendios 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, y por lo tanto, sobrepasar 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 consuta 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

0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
    select 'crack', usesysid, 't','t','crack'
    from pg_shadow where usename='postgres';
--

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'
                  ORDER BY 
$order LIMIT $limit$offset;";
$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:

'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--

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 con ‘ or uid like’%admin%’; — hacia $uid para cambiar la contraseña del administrador, o simplemente cambiar $pwd a “hehehe’, admin=’yes’, trusted=100 “ (con un espacio al final) para ganar más privilegios. Entonces la consulta será cambiada así:

<?php

// $uid == ' or uid like'%admin%'; --
$query "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --";

// $pwd == "hehehe', admin='yes', trusted=100 "
$query "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE
...;"
;

?>

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á:

<?php

$query  "SELECT * FROM products
                    WHERE id LIKE '%a%'
                    exec master..xp_cmdshell 'net user test testpass /ADD'--"
;
$result mssql_query($query);

?>

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.

 

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.
  • 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 is_numeric(), 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);

    ?>

  • Encierre entre comillas cada valor no-numérico provisto por el usuario que sea pasado a la base de datos filtrado con la función de cadena específica de la base de datos (Ej. mysql_real_escape_string(), sqlite_escape_string(), etc.). Si una función de escape (o de filtrado) de cadena específica de la base de datos, o un mecanismo similar no está disponible, las funciones addslashes() y str_replace() podrían ser útiles (dependiendo del tipo de la base de datos). Vea el primer ejemplo. Como lo muestra el ejemplo, agregar comillas a la parte estática de la consulta no es suficiente, lo que hace que esta consulta sea facilmente vulnerada.
  • 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.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *