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 seguridadLa 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:
- 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.
- "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).
- 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 JavascriptFinalmente, 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 finalEl 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 relacionadaYa 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!