All your buckets are belong to us: DOSeando PHP a través de arrays asociativos
Hace unas horas andaba yo pasando el rato por /r/programming cuando me encontré con un post sobre la posibilidad y las consecuencias de forzar externamente colisiones dentro de los arrays asociativos de PHP, es algo tan... ¿abrumador? que hay que probarlo para ver el peligro que representa, veamoslo.
Nota: El día 28 de este mes hubo en el 28C3 una charla que tiene mucho, ¡todo! que ver con esto, es muy recomendable hecharle un vistazo, enormemente interesante.
El peligro se agrava por una combinación de varios factores que PHP agrupa:
-
Se puede adivinar de forma trivial el hash de un entero, el mismo.
-
Hay algunos arrays donde el usuario tiene total poder para crear a su antojo:
$_GET
,$_POST
y$_COOKIE
.
Imaginad si lanzamos contra un servidor unas cuantas peticiones con un poco de mala leche, como puede ser un poco engorroso hacerlo a mano, he aqui un script para facilitar la tarea prueba_hashmap_php.py... no es bonito, no es elegante, pero tampoco lo pretende.
El script maneja varias posibilidades, si ha de esperar o no por la respuesta del servidor y cuanto esperará entre petición y petición, se pueden modificar en las lineas 10 y 11, además se puede pasar por parámetros el número de valores a enviar, el número de envios que se realizaran, y el número de hilos que harán la operación a la vez (en ese orden).
Bien, ahora pasemos a las pruebas, el "atacante" es un netbook cutrillo incapaz de tirar con un emulador de N64 (por dar una idea), la "víctima" es un Cuad Core, no es lo último de lo último pero se debería portar bien, ¿no?.
Pues no.
Lanzando un ataque de una sola petición, con 50000 elementos, y esperando por el servidor, se obtiene la siguiente salida:
1 2 3 4 5 6 7 8 9 |
|
Creo que queda claro el problema, se tardó apenas dos décimas de segundo en mandar los datos (sin contar el tiempo para preparar la petición, que solo se hace una vez) y sin embargo el servidor no solo tardó 60 segundos, deteniendose con un error 500 (Internal server error), sinó que durante ese tiempo un núcleo estuvo al 100%.
¿Y si repetimos la experiencia pero ahora con cuatro hilos?
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Y durante ese tiempo cuatro (de cuatro) núcleos al 100%.
Por si alguien se esperaba un script complejo que explicara estos tiempos, aquí está al que se le hacen las peticiones:
1 2 3 |
|
Pero la cosa no se queda en una denegación de servicio con necesidad de muy poco tráfico, se puede poner peor, ¿que pasa si inmediatamente después de enviar la petición al servidor desconectamos y mandamos otra?
1 2 |
|
Si lo lanzamos con bloques de 50000 valores, con un infinito número de intentos (-1 servirá), y digamos... 10 hilos, veremos algo muy interesante, dejando de lado que logicamente todos los núcleos se ponen a 100 y que al principio requiere un ancho de banda considerable ~3mb (después de menos de un minuto apenas hace falta 1kb para mantenerlo), el gasto en memoria aumenta, al principio muy rápido, después menos, pero al cabo de ~10 minutos habrá consumido casi 1 Gigabyte y todo esto mientras un simple netbook no le dedica ni un 1% del procesador al ataque.
Solución
Como dice en el primer post al que se hace referencia en la entrada, ya hay un commit en el SVN de PHP que añade una directiva max input vars para limitar los parametros que se pueden enviar a la vez, que por lo que dice el post, llegará con 5.3.9 (en los repos de trisquel el que está es la 5.3.5), teóricamente otra opción sería hechar mano del parche Suhosin, que viene por defecto con Debian y derivadas pero después de probarlo no puedo decir que fuera a mejor :/
Eso es todo, hasta la proxima.