Too Cool for Internet Explorer

Evitar SQL Injection en PHP

Hora y Fecha: Junio 9, 2005 @ 3:02 am Autor: Moisés Maciá
Categorías:
2,366 views

Los ataques de SQL Injection a las aplicaciones web están a la orden del día y aquel que no proteja mínimamente sus scripts para evitar estos problemas, simplemente esta muerto en cuestión de minutos.

Hay una serie de recomendaciones y buenas costumbres a la hora de programar el acceso a datos y evitar en lo posible los ataques de SQL Injection (algo que no deben conocer en la empresa privada porque desde que estoy trabajando veo cada burrada que me deja muerto …), yo me centraré en PHP que es lo que más utilizo pero es igualmente valido para JSP, Python o cualquier lenguaje.

Principio del mínimo privilegio

Crear un usuario que sólo pueda acceder a la base de datos que le toca y sólo pueda ejecutar las sentencias que necesite. Muchas veces (el 100% de las veces para no mentir) he visto que la aplicación accede desde la cuenta del administrador de la base de datos, si ocurre alguna incidencia el desastre está garantizado.

Consultas concretas

Una consulta devuelve los datos que se necesitan y en la forma en que se necesitan, nada mas. Terminantemente prohibido utilizar consultas con comodín como SELECT * FROM customers en producción, en su lugar: SELECT name,dni FROM customers.

Caso verídico: tenemos en una BD un montón de registros con un campo fecha y necesitamos sacar los años de esas fechas, un problema típico a mas no poder de SQL. Atención a la solución que hicieron en su día.

Con un la consulta SELECT * FROM articles ORDER BY id DESC lo sacamos todo.
Recorremos todos los campos de cada una de las filas hasta encontrar el campo fecha que es el que nos interesa (!!).
Parseamos la fecha y extraemos el año.
Insertamos el año en un vector comprobando que no hayan duplicados.
Repetimos todo esto por cada fila devuelta en la consulta.
Ordenamos el vector de años.

¡Ole sus huevos! No se me ocurre otra forma peor de hacerlo aunque probablemente exista, la estupidez humana es insondable.

Si utilizamos continuamente el comodín estamos obteniendo mas datos de los que necesitamos (daremos mas información al atacante si tiene éxito).

No es únicamente una recomendación para evitar las SQL injections, imaginemos que el día de mañana el administrador de la base de datos decide añadir mas columnas a la tabla o un campo BLOB o se incremente el número de consultas por segundo; a poco que el campo binario ocupe 100Kb y hagamos una ráfaga de 1000 consultas tendremos 100Mb de datos que no nos interesan para nada viajando por la red y saturando recursos.

Filtrar errores

Cuando se produce un error en una consulta SQL normalmente el motor de la base de datos devuelve la descripción del error y la consulta que ha fallado. Esta información es muy valiosa para depurar el programa pero no se debe dejar a la vista en un producto final ya que ofrece mucha información sobre la estructura de la base de datos y el propio motor de la base de datos.

En PHP y en la practica totalidad de los lenguajes de programación se puede reemplazar el manejador de errores y sustituirlo por una rutina propia, con esto podemos mostrar al usuario un mensaje del tipo Se ha producido un error de acceso a datos, pongase en contacto con blabla … y guardar un volcado de variables y la descripción detallada del error para nosotros.

En su día ya hablé de una clase de gestión de errores que hice para PHP justamente por este tipo de cosas.

Comprobar parámetros de entrada

Este es el apartado mas importante, normalmente componemos una consulta SQL a partir de unos parámetros que llegan de un formulario, mediante los métodos GET o POST del protocolo HTTP. Todos los parámetros provenientes de estos métodos son potencialmente peligrosos y tenemos que desconfiar al máximo.

Hay gente que filtra los parámetros en el lado del cliente utilizando Javascript, esto da una falsa sensación de seguridad. Es muy sencillo pasar por alto la comprobación e inyectar los datos que queramos sin ningún problema. La única solución definitiva es filtrar en el lado del servidor.

Normalmente se presentan tres casos: comprobar que el dato no este vacío, comprobar si el dato es numérico y comprobar si el dato es una cadena de caracteres.

Debido a que PHP es un lenguaje de tipado débil hay que tener especial cuidado con estas comprobaciones.

Comprobando si el dato está

Podemos utilizar una serie de sentencias if() pero lo mas rápido y seguro es utilizar la función empty() de PHP que comprueba números, cadenas y lo que le eches.

Comprobando datos numéricos

Si el dato que esperamos es numérico lo mas rápido y sencillo es hacer una conversión de tipo por la fuerza: si el dato es legitimo no pasara nada, en otro caso obtendremos una representación numérica valida que no afectara a la consulta.

$id = (int)$_GET['id'];

O el tipo que toque en cada caso.

Comprobando cadenas de caracteres

Si la cadena que esperamos tiene un formato predefinido podemos utilizar una expresión regular para validarlo con la función grep_match().

En otro caso tenemos que evitar los “caracteres mágicos” que pueden reventar la consulta, estos son: comillas simples, comillas dobles y doble guión. Aplicaremos la política de sustituirlos por una cadena de escape si nos encontramos con ellos o mejor, si nuestra base de datos es MySQL, utilizar la función mysql_escape_string() que se encarga de limpiar todos estos caracteres antes de insertarlos.

Podemos mejorar la seguridad utilizando expresiones regulares que detecten palabras reservadas del lenguaje SQL como insert, update, delete, ... pero esto ya me parece excesivo :)

¿Alguien sabe algún truqillo más?





« Anterior post: Apple y la comunidad libre, un poquito de por favor | Próximo post: Primer examen »

10 Comentarios para “Evitar SQL Injection en PHP”

Tuxiradical
12 de Junio de 2005 a las 4:55 pm    

Yo para saber si es un numero o no, lo divido entre 2.

if($variable/2){
// Codigo si es un numero
}else{
// Codigo si es un texto
}

Si me pones en un aprieto y pones 2/$variable matamos dos pajaros de un tiro… ya que si no tiene nada creo que tambien vale a 0 xDD 2/0 -> ERROR, osea false ;)

Tuxiradical
12 de Junio de 2005 a las 5:51 pm    

Por cierto… pondre en practica lo del *, lo suelo hacer mucho :P

Moises
12 de Junio de 2005 a las 5:59 pm    

Ay, ay, ay!!! ;)

Jurix
16 de Junio de 2005 a las 8:07 pm    

Sí que sé otra cosilla, existe una función que marca las comillas simples que pueden ser muy peligrosas para una inyección sql, creo que se trataba de addslashes.

Otra práctica habitual para no tener que marcar y desmarcar cadenas sería establecer unos valores correctos a las magic quotes de en el php.ini

Un saludo, espero no haber metido la pata :D

P.D.: La clase de manejo de errores en php5, por mis webs que un día te la mando para que la publiques, cuando tenga tiempo de prepararla.

Moises
16 de Junio de 2005 a las 8:43 pm    

Imagino que aplicando la funcion mysql_escape_string() no hace falta la funcion addslashes().

Y lo de los magic quotes, tienes razón, se me paso comentarlo :)

javier
2 de Diciembre de 2005 a las 4:34 pm    

lo que me resulto mas facil para mi es hacer una funcion que limpie las variables, que saque las palabras INSERT, DROP, DELETE, las comillas, etc.

gato
9 de Enero de 2006 a las 6:05 am    

Javier, supongo que en ese “etc” incluyes UNION y SELECT. Si no es así te recomiendo que lo hagas, y las llaves, paréntesis y corchetes. Creo que en realidad lo mejor es usar la función mysql_scape_string() pero si te pones a capar, mejor utiliza expresiones regulares y habilita solo la posibilidad de que introduzcan numeros, letras y vocales acentuadas…
Bueno, es mi consejo.
Saludos

Leo
31 de Julio de 2006 a las 5:57 pm    

Oye pero eso de que los 100Mb viajan por internet es completamente falso, lo que hacen es ocupar tu RAM, pero no mas. Lo mejor es siempre validar los datos en los cuales vas a hacer consultas. Si son numericos, para que dividir si existe is_numeric() o is_int? si no puedes validar entonces usar mysql_scape_string()

Moisés Maciá
31 de Julio de 2006 a las 10:59 pm    

Si tu servidor de bases de datos esta en otra máquina como es lo habitual en entornos en producción, los datos si que viajan de un lado para otro.

Tambien te recuerdo que PHP no es un lenguaje tipado asi que cualquier comprobacion que hagas nunca esta de mas.

Enlaces de la semana 3 — Viciao2k3
9 de Agosto de 2008 a las 9:32 pm    

[...] Nube de tags en 3D gracias a Flash y a WP-Cumulus [...]


Bad Behavior has blocked 348 access attempts in the last 7 days.