viernes, 30 de diciembre de 2011

’All your buckets are belong to us’ tumbando 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 dia 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 vídeo está aquí [28c3: Effective Denial of Service attacks against web application platforms].



Todo este tema 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, tampoco lo pretende, pero tened cuidado a donde apuntais que hace daño ;D ).

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 Nintendo 64 (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:
[0] 500 > 0.192291021347 | 60.0323388577 <

----- Data -----

Codes:
500: 1 times

Average uploading time: 0.192291021347
Average downloading time: 60.0323388577
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] 500 > 0.75856590271 | 60.0828011036 <
[0] 500 > 0.740755081177 | 62.4277861118 <
[3] 500 > 0.806277036667 | 67.9619438648 <
[2] 500 > 0.784065008163 | 69.3936538696 <

----- Data -----

Codes:
500: 4 times

Average uploading time: 0.772415757179
Average downloading time: 64.9665462375
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:
<?php
    echo 'Jau!';
?>
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?
wait_for_server = False # Wait for the server to answer?
wait_between = 0.5 # Seconcs to wait between connections

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 :/

Y eso es todo, a portarse bien ;)

5 comentarios:

  1. Muy bueno la verdad, me gusto mucho leer esta entrada.

    el LOIC WEB que hizo anonymous funcionaba con ese tipo de ataque si no recuerdo mal hacia variables por get a lo loco.

    ResponderEliminar
    Respuestas
    1. Me alegro de que te haya gustado :)

      No creo que el LOIC funcionara asi, por que para esto hace falta buscar unas variables concretas, ademas por GET no se pueden mandar suficientes para que se note (la longitud de la url es limitada).

      Eliminar
  2. no entendi, que hay que configurar para que funcione, y todas las webs con php funcionan?

    ResponderEliminar
  3. Un video tutorial estaria muy bien y seria muy eficas en mi caso y en el de muchos. Un Saludo

    ResponderEliminar
  4. yo soy girlhack y me llama la atencion de que los hackers debemos proteger como soldados y luchar contra los falsos gobernantes yo soy de argentinian la plata y considero trabajar en hackear bancos para yo mostrarles q las seguridad de ellos no es una proteccion

    ResponderEliminar