viernes, 26 de agosto de 2011

DoS con SQLi - Caso Fisimash

Ya ha pasado buen tiempo desde que cerró la página web de fisimash. Así que me he dispuesto a escribir sobre la vulnerabilidad que hacía posible tirarla en unos cuantos minutos.

Primero recordemos cómo funcionaba la web: Se presentaban 2 fotos, el usuario elegía una haciéndole clic, el sistema aumentaba la cantidad de veces ganadas a la foto elegida y la cantidad de veces perdidas a la otra, finalmente se mostraban otras dos fotos y se repetía el proceso.

Eso es lo que cualquier usuario veía ¿Pero como funcionaba en realidad? Bien, no hay que ser un superhacker para deducirlo. Todas las fotos tenían un registro en una base de datos y estaban asociadas a un identificador (id) que era un simple número entero. Luego cuando se presentaban las fotos, estas tenían un link a una URL que enviaba el id de la foto ganadora y el id de la foto perdedora. Así, la foto A enviaba su id como ganador y el id de B como perdedor y la foto B enviaba su id como ganador y el de A como perdedor. Veamos:

Foto A (id 79):
http://www.fisimash.260mb.com/Rate.php?winner=79&loser=199

Foto B (id 199):
http://www.fisimash.260mb.com/Rate.php?winner=199&loser=79

Vemos claramente que en la URL se envían las variables "winner" y "loser" con los ids ganador y perdedor respectivamente.

Llegado a este punto, encontramos la primera vulnerabilidad ¿Qué pasa si enviamos repetidas veces un id como ganador o como perdedor? ¿Hay algún método de verificación para determinar si ya votamos? ¡Pues no! Sucede que podías hacer que cualquier foto aparezca en el top 10 o sacarla de ahí simplemente enviando su id como ganador o perdedor muchas veces.

Eso se podía hacer fácilmente con el siguiente script bash, solo hay que pasarle los parámetros adecuados ;)

#!/bin/bash
if [ $# -ne 3 ]; then
	echo "Uso: $0 <id_ganador> <id_perdedor> <#envios>";
else
	for i in $(seq 1 $3); do
		wget --no-cache --spider "http://www.fisimash.260mb.com/Rate.php?winner=$1&loser=$2";
	done
fi

Bien, pero continuemos analizando. Evidentemente los puntos de veces ganadas o perdidas se deben actualizar en la base de datos y para identificar que registro se debe actualizar se emplea el id enviado. Por lo que la consulta SQL que actualiza los puntos de la foto debe incluir ese id. Pregunta ¿Habrán validado correctamente sus entradas? ¡Pues no! Otro fail. Sucede que podemos manipular las variables winner o loser para inyectar código SQL y alterar la consulta que actualiza los puntos. Por ejemplo:

Imaginemos que la consulta que actualiza las veces ganadas es:

UPDATE fotos SET ganadas=(ganadas+1) WHERE id_foto = $winner;

$winner es la variable que podemos manipular, así que si llamamos a la URL de esta forma:

http://www.fisimash.260mb.com/Rate.php?winner=79 OR 1=1&loser=NULL

La consulta quedará así:

UPDATE fotos SET ganadas=(ganadas+1) WHERE id_foto = 79 OR 1=1;

Y en consecuencia se le aumentará un punto a todas las fotos y no solo a la que tiene id 79.

Bien ya entendida la vulnerabilidad... la pregunta es ¿Cómo podemos utilizarla para tirar la web? Si alguien ha escuchado de #RefRef (la nueva DoS Tool de Anonymous) seguro ya estará imaginando algo }:] (no, no tengo una copia de ese software)

Pues, la idea es inyectar repetidas veces y en paralelo muchas consultas que consuman excesivos recursos de procesamiento en el servidor para provocar la denegación de servicio. Pero considerando que fisimash utilizaba un hosting gratuito, no era preciso agotar los recursos de todo el servidor sino tan solo los recursos asignados a la cuenta de fisimash. Es decir, inyectamos una consulta relativamente pesada, el servidor de hosting nota que fisimash esta usando más recursos de los permitidos a una cuenta gratuita y en consecuencia bloquea la página por algún tiempo.

Para hacer esto podemos utilizar por ejemplo la función "benchmark" de MySQL. Esta función permite ejecutar una orden un determinado número de veces. Por ejemplo:

SELECT benchmark(1000000,md5('foo'));

Eso provocará que MySQL calcule el hash MD5 de 'foo' un millón de veces... en mi PC demora 0.37 segundos xD. Así que necesitaremos algo mucho más pesado. Quizá un benchmark dentro de otro benchmark dentro de otro más que calcule el MD5 del SHA del MD5 de 'foo' }xD.

El problema es que por alguna razón la función benchmark no parecía funcionar en el servidor. Quizá la deshabilitaron o algo así.

Otra forma de hacer consultas pesadas es usando múltiples joins (consultas a múltiples tablas relacionadas por algún campo en común), de echo tengo muy malas experiencias haciendo consultas que llegaban a demorar hasta 15 minutos solo para sacar un reporte con iReport :S.

Sin embargo lo que hice fue algo mucho más sencillo pero que dio resultado. Inyecte un retardo de tiempo con "sleep". Esta función sirve para indicarle a MySQL que espere un determinado número de segundos. En realidad sleep no consume recursos del procesador, pues lo que hace es dormir el proceso por el tiempo indicado y mientras tanto el procesador puede ocuparse de otras tareas. Pero entonces ¿Por que se cae la web? Yo supongo que el sistema que detecta el consumo de recursos no tiene en cuenta esto sino solo cuanto tiempo se demora MySQL en responderle a PHP. Si demora mucho debe ser por que esta trabajando ¿No? Aunque en este caso es porque está durmiendo xD.

La consulta que inyecte fue así:

URL:
http://www.fisimash.260mb.com/Rate.php?winner=NULL OR sleep(5)=0&loser=NULL

SQL:
UPDATE fotos SET ganadas=(ganadas+1) WHERE id_foto = NULL OR sleep(5)=0;

Sleep siempre devuelve 0, así que esto es como el OR 1=1, provocará que se actualicen todos los registros de la tabla. Si habían 199 fotos (199 registros) y por cada uno esperará 5 segundos en total son algo más de 15 minutos lo que va a demorar la consulta. Antes que terminara de ejecutarse, la web ya estaba bloqueada.

Fig. 1 - Fisimash fuera de juego.

 Algo mucho más interesante que se puede hacer explotando la inyección SQL es obtener el esquema y la información de la base de datos... aunque para ello había que emplear gran cantidad de consultas y la web se bloqueaba antes que puedas sacar nada :(

Para terminar con esto, les dejo un vídeo que muestra la vulnerabilidad. En este caso se inyecta el SLEEP dentro de un IF para hacer notar que cuando la condición del IF es verdadera se ejecuta el retardo y no en caso contrario.

(sugerencia, para notar mejor el retardo, fijarse en el icono de la pestaña del navegador)




Saludos...

No hay comentarios:

Publicar un comentario