Solución Semana 4: "Sistema Modular" (V de V)

El punto V de los requerimientos de la Semana 4 decía:

Seguridad: el sistema actual confía de los datos que recibe, lo cual puede convertirse en un problema de seguridad a través de un ataque del tipo "sql injection". Proponer una solución a este problema.

En el propio manual oficial de PHP hay un capítulo exclusivo sobre Seguridad (IV) y dentro existe una sección sobre Bases de Datos y una sub-sección que dice concretamente Inyección de SQL:

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.

Esta parte la vivimos la comienzo de la puesta en producción del sistema de registro de usuarios para este taller piloto de PHP5, donde uno o más saboteadores intentaron borrar la base de datos usando este tipo de ataque.

Veamos qué podemos hacer para reforzar la seguridad

La esencia es evitar completamente que los datos lleguen en bruto desde la url o de un formulario hacia la base de datos, verificando cada dato y asegurando que la construcción final del SQL no sea alterada para modificar el funcionamiento normal de nuestro sistema.

La lista a continuación no es extensa, solo intenta ser una introducción al tema:

  1. Verificar los tipos de datos que se reciben: controlar cada uno de los parámetros según el tipo de dato que se espera. Si es un campo numérico del formulario se deberá verificar que sus datos sean así, numéricos, evitando que agreguen una cadena de texto (String) con código SQL maligno.
  2. "Escapear" caracteres riesgosos: comilla simple ('), comilla doble ("), barra invertida (\) y NULL (el byte NULL). Hay que tener en cuenta que si los datos son grabados en la base de datos con "caracteres escapeados" (addslashes), hay que hacer el proceso inverso para presentarlos en pantalla (quitando las "\" con stripslashes).
  3. Agregar procesos que verifiquen sentencias SQL: otra forma de reforzar es verificar que cada cadena de texto que debamos ejecutar en la base no contenga determinadas palabras que no estamos usando para una determinada operación. Por ejemplo, si deseamos obtener datos sin modificar la base (SELECT) no deberíamos permitir que se ejecutaran comandos como "INSERT", "DELETE" y "UPDATE" (ni ningún otro que permita saber información interna ni de las estructuras de las tablas), y/o directamente prohibir que se puedan agregar "AND", "OR" o un ";" (lo que permite agregar a continuación otra sentencia SQL).
Estimo que la mejor forma de tener controlado este proceso es separar los datos por un lado, la generación de las consultas SQL por otro, y evitar tener un proceso genérico donde todo pase por el mismo lugar.

Algunos ejemplos

Nota: si la imagen no se visualiza claramente, presionar sobre ella para ampliarla.


Validar el tipo de valor que recibo por parámetros: para luego pasarlo al armado de la consulta.

En este caso esperamos que los valores posibles son Null o un Integer, si es el primero hacemos un "SELECT * FROM usuarios" y si es el segundo esperamos que sea un número de usuario. Verificamos que no sea nulo, y luego nos aseguramos que sea un valor numérico.

Crear un método "quote" en la clase BaseDeDatos:
el término quote se usa en muchos frameworks a la hora de crear un proceso que "escapee" (si alguien conoce castellanización que avise) las variables que se usan en una sentencia SQL.

En este caso verificamos la existencia de funciones que ya realizan automáticamente el "escaping", de lo contrario lo hacemos manualmente. De la misma forma existe una función de PHP específica para la base MySQL, lo cual nos ataría a futuro si queremos cambiar de base de datos.

Separar los datos de la consulta sql:
para poder procesar fácilmente cada dato.


En este caso tomamos nuestro método ejecutarSQL, que solo recibía una sentencia sql, y que ahora recibe por un lado el esqueleto de la sentencia y por otro los valores a ser insertados en la construcción de la sentencia final. Su funcionamiento es similar al de cualquier sistema de templates.


Aquí pasamos por referencia (&) la sentencia original para modificarla, sustituyendo nosotros cada uno de los valores en los lugares que corresponde insertar y ejecutando la "sanitización" de cada valor con el método "quote".


Nuevamente la definición del método "quote".

Este es un ejemplo de cómo funcionaría en una situación donde queremos solo obtener datos de la base de datos, definiendo primero un array de datos asociando la clave PAR01 (parámetro 1) y el valor recibido por la url. Luego, se armar el template SQL y colocando el PAR01 donde debería ir para que el proceso de armado sustituya de forma segura por el valor correspondiente luego de "desinfectarlo".

Este es otro caso, el más complejo, donde hay alteración de la base de datos y existe más de un parámetro. La lógica es simple, y como afirmé más arriba, es lo mismo que utilizar un sistema de templates o plantillas, defines una marca o variable que será sustituida por el valor que tu asignes, manteniendo el resto sin alterar.

Validación con Javascript


Finalmente, no quiero olvidarme comentar el tema de las validaciones desde el propio cliente con Javascript (lo vi comentado en una de las soluciones entregadas por algunos participantes del taller). La solución de este lado del proceso puede considerarse un complemento, pero no la solución más segura. Estimo que cualquier usuario que quiera hacer un ataque de este tipo lo primero que probará si encuentra controles del lado del cliente es deshabilitar el Javascript en el navegador. Sirve como complemento, pero no es la mejor opción ni la más segura.

La recomendación final

El tema de la seguridad es una tarea muy especializada, no sirve agregar un par de métodos con algunas funciones, habría que auditar constantemente todo el código nuevo que se agrega al sistema. Para este caso particular, el SQL Injection, como afecta exclusivamente a la persistencia de datos en un motor de base de datos, lo mejor es tener una "capa de abstracción" de terceros que tenga bien resuelta toda esta problemática. Por ejemplo, podemos usar MDB2 del framework Pear o directamente PDO que incorpora el propio PHP5.1 en adelante.

Documentación relacionada
Ya se encuentra la planilla actualizada, como así también el repositorio con los fuentes y como siempre, en los comentarios de esta entrada se pueden sacar las dudas sobre este tema :-)

PD: en las próximas horas se estarán haciendo los anuncios con los equipos de desarrollo armados. En primera instancia estarán involucrados los participantes que sobrevivieron todo el proceso del taller (¡Felicitaciones! ;-)) y posteriormente iré analizando la incorporación de más colaboradores en base a la evolución de las primeras pruebas y teniendo en cuenta la lista de registrados al taller.

¡POR FIN, CERRAMOS LA SEMANA 4!

3 comentarios:

eder dijo...

Buenas tardes, pasando por la red de ocioso me encontre con tu blog y con este artículo, lo he leído y creo que lo mejor si como tu dices es tener todo separada (modúlos que se encargan de verificar todo), pero creo que lo que te ha faltado a mi punto de ver es ke siempre tenemos que validar las entradas, asi como tu lo dijiste si esperamos números, validemos números!!!!. Creo que este problema de inyección se resuelve de una manera sencilla con expresiones regulares ya que defines el conjunto de caracteres, simbolos de tu lenguaje (parametros válidos para tus transacciones con la bd) y pues si esta bien, lo que has dicho de addslashes, escapar símbolos especiales pero no es la solución ya que hay lo que se llama código unicode y otras diferentes representaciones para poder dar este ataque.

Espero y podamos discutirlo

Atte Eder LiRA

Sergio David López Rodriguez dijo...

Que tal !

Escribe una vez mas Sergio López con 1 sugerencia constructiva...

Creo que una solución importante y profesional seria la creacion y llamada de stored procedures, como sabes mata muchos pajaros de un solo tiro, en la ultima version de mysql ya existe el soporte de creacion de stored procedures (asi tambien en la extension mysqli de php) y creo seria una ventaja total ante la seguridad y asi de paso evitarnos un poco de hard code, haciendo mas legible nuestro codigo.. en fin muchas ventajas que se hablan en la red ... http://searchsqlserver.techtarget.com/originalContent/0,289142,sid87_gci1052737,00.html

Saludos

ATTE
Sergio López

Damián dijo...

Buenas, soy programador PHP hace un par de meses, y en la empresa que estoy trabajando el desarrollo es un tanto desordenado, me interesó mucho la idea de la web modular, los patrones, etc. Quería saber en donde están las fuentes, pq nombrás un repositorio pero no lo encuentro.
Haces un gran aporte a la comunidad PHP con este blog.
Muchas Gracias.
Damián

Entradas populares