All your buckets are belong to us: DOSeando PHP a través dos arrays asociativos

Fai unhas horas andaba eu pasando o rato por /r/programming cando topei cun post sobre a posibilidade e as consecuencias de forzar externamente colisións dentro dos arrays asociativos de PHP, é algo tan... ¿abrumador? que hay que probalo para ver o perigo que representa, vexámolo.

Nota: O día 28 de este mes houbo no 28C3 unha charla que ten moito, todo! que ver con isto, é moi recomendable botarlle unha ollada, enormemente interesante.

O perigo agravase por unha combinación de varios factores que PHP agrupa:

  • Pódese adiviñar de forma trivial o hash dun enteiro, el mesmo.

  • Hay algúns arrays onde o usuario ten total poder para crear o seu antoxo: $_GET, $_POST y $_COOKIE.

Imaxinade se lanzamos contra un servidor unhas cantas petición cun pouco de mala leito, como pode ser un pouco engorroso facelo a man, aqui tedes un script para facilitar a tarefa prueba_hashmap_php.py... non é bonito, non é elegante, pero tampouco o pretende.

O script manexa varias posibilidades, se esperará ou non pola resposta do servidor e canto esperará entre petición e petición, isto pódese modificar nas liñas 10 e 11, ademáis pódese pasar por parámetros ó número de valores que se enviará, ó número de envios, e ó número de fios que farán a operación á vez (nese orde).

Ben, agora pasemos ás probas, o "atacante" é un simple netbook incapaz de tirar cun emulador de N64 (para dar unha idea), a "víctima" é un Cuad Core, non o último do mercado pero debería portarse ben, non?.

Pois non.

Lanzando un ataque dunha soa petición, con 50000 elementos, e esperando polo servidor, obtienese a seguinte saída:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[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 o problema, tardouse apenas dúas décimas de segundo en mandalos datos (sen contar o tiempo para preparala petición, que so se fai unha vez) e nembargantes o servidor non so tardou 60 segundos, deténdose cun error 500 (Internal server error), senón que durante ese tempo un núcleo estivo ao 100%.

E se repetimos a experiencia, pero agora con catro fios?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[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

E durante ese tempo catro (de catro) núcleos ao 100%.

Por se alguén esperaba un script complexo que explicara estes tempos aquí está ó que se lle fan as peticions:

1
2
3
<?php
    echo "Jau!";
?>

Pero a cousa non queda nunha denegación de servicio con necesidade de moi pouco tráfico, pódese por peor, que pasaría se inmediatamente despois de envia-la petición ó servidor desconectamos e mandamos outra?

1
2
wait_for_server = False # Wait for the server to answer?
wait_between = 0.5 # Seconcs to wait between connections

Se o lanzamos con bloques de 50000 valores, cun infinito número de intentos (-1 serverá), e digamos... 10 fios, veremos algo moi interesante, deixando de lado que loxicamente tódolos núcleos poñense a 100 e que o principio require un ancho de banda considerable ~3mb (despois de menos dun minuto apenas fai falta 1kb para mantelo), o gasto de memoria aumenta, o principio moi rápido, despois menos, pero o cabo de ~10 minutos consumirá casi 1 Gigabyte e todo isto mentres un simple netbook non lle adica nen un 1% do procesador o ataque.

Solución

Como di no primer post o que se fai referencia na entrada, xa hay un commit no SVN de PHP que añade unha directiva max input vars para limitalos parámetros que se poden enviar á vez, que polo que di o post, chegará con 5.3.9 (nos repos de trisquel a que está e a 5.3.5), teóricamente outra opción sería botar man do parche Suhosin, que ven por defecto con Debian e derivadas, pero despois de probalo non podo dicir que vaia mellor :/

Iso é todo, ata a próxima.