jueves, 16 de abril de 2009

¿Fallo de programación en mod_evasive?

mod_evasive es un módulo de apache que sirve para “esquivar” ataques de denegación de servicio. Detecta cuando un cliente está realizando excesivas peticiones y bloquea temporalmente su acceso o realiza otra acción configurable.

Hay muchos “manuales” para instalar y configurar mod_evasive. Lo mas importante son los valores configurables en httpd.conf para definir su comportamiento, por defecto viene configurado así:

DOSHashTableSize 3097 DOSPageCount 2 DOSSiteCount 50 DOSPageInterval 1 DOSSiteInterval 1 DOSBlockingPeriod 10

Significando de DOSPageCount y DOSPageInterval, sacado de emezeta.com:

  • DOSPageCount / DOSPageInterval: Máximo (umbral) que se debe alcanzar para ser incluído en la lista de bloqueados. En este caso el objetivo será una página concreta. (Entiendase como «Máximo de DOSPageCount páginas en DOSPageInterval segundos»).

    Por defecto el máximo está establecido a 2 páginas por segundo.

Genial, es muy útil, aunque no es raro que un cliente legítimo realice en un momento puntual 2 peticiones a una misma página en menos de un segundo, abriendo pestañas, algún script de recarga, o un php de acceso a imágenes…

Me parece mucho mas adecuado que bloquee una IP cuando ese índice de peticiones por segundo se perpetua en el tiempo. Por ejemplo 20 peticiones en 10 segundos, 2 peticiones por segundo durante 10 segundos (si, aprendí a multiplicar recientemente).

Así que establecí los siguientes valores en la configuración de mod_evasive:

DOSPageCount 20 DOSPageInterval 10

apache reload y a gozar.

Al rato vi como se estaban bloqueando IPs que en principio no deberían ser bloqueadas. Verifiqué el access_log de apache y efectivamente, estaban realizando como mucho 5 peticiones cada 10 segundos. Un valor 4 veces menor(incluso sé dividir), muy lejano al máximo de 20 peticiones cada 10 segundos que hemos configurado. Lo verifiqué manualmente y en efecto, realizando una petición cada 5 segundos a las 20-21ava petición mod_evasive me bloqueaba.

Probé con diferentes valores, rango mas amplios, reiniciar apache, reinstalar y nada, esa cosa no funcionaba como decía. Maldita sea !

Estaba claro, yo no estaba entendiendo bien que significaban esos valores, porque sería muymuy raro que un módulo tan usado fallase en algo tan básico, ¿no? Así que me dispuse a leer la documentación oficial, el mítico README, que decía mas o menos lo mismo que lo que pegue arriba. Así que se me ocurrió la locura de echar un ojo al código fuente: “quizás ahí este explicado o vea una formula que explique su funcionamiento”.

El código, localizado en mod_evasive20.c, se puede entender, al menos la parte donde hace la verificación de si una IP está atacando o no.

image

Resumen de los valores de las variables al realizar la comprobación:

  • t: Tiempo actual en segundos. Inicializado como: time_t t = time(NULL);
  • n->timestamp: n es una tabla que se usa para relacionar URL-Numero de peticiones-Timestamp. timestamp es una “marca de tiempo” que se establece en la primera petición y es actualizada cada DOSPageInterval segundos, en nuestro caso cada 10 segundos.
  • page_interval: Tiene el valor establecido en httpd.conf, DOSPageInterval. Intervalo en segundos durante el cual se medirá si se ha alcanzado el límite de peticiones. Como acabamos de decir, 10 segundos en nuestro caso.
  • n->count: Número de peticiones realizadas a una misma página web, el valor se resetea cada DOSPageInterval segundos, en nuestro caso cada 10 segundos.
  • page_count: Tiene el valor configurado en httpd.conf, DOSPageCount. Máximo número de peticiones que se puede realizar a una misma página web. En nuestro caso 20.

Así podemos ver como la primera parte de la condición mira si desde el timestamp hasta el momento actual han transcurrido menos de 10 segundos, si esto ocurre pasa a la segunda parte de la condición que verifica si el número de peticiones realizadas es mayor que el máximo numero de peticiones permitidas.

Todo perfecto en teoría, pero a mi me funcionaba mal, así que hice un debug chapucero usando el macro LOG que añade un mensaje al log del sistema /var/log/syslog.

image

Recompilamos ./apxs -cia mod_evasive20.c Recargamos la configuración de apache /etc/init.d/apache reload

Y empezamos a hacer una petición cada 5 segundos mientras miramos el syslog tailf –f /var/log/syslog | grep mod_evasive

image *Paint art

Como esperábamos cada 5 segundos se realiza una petición, hasta alcanzar las 6 peticiones de un máximo de 20. Pero esto sigue hasta que…

image

Pero como es posible, está ****** nos banea con 20 peticiones en 200 segundos !!! El error parece estar en que nunca resetea el n->count y en teoría cada 20 segundos debería resetearlo. Veamos en el código en que parte lo resetea…

image

Cuando el tiempo actual en segundos, t, menos la marca de tiempo inicial, n->timestamp, es mayor que el intervalo de tiempo, 10, se resetea n->count. Es decir, una vez pasados los 10 primeros segundos debería resetear el contador n->count, pero en los “logs” vemos que hay un problema, n->timestamp se actualiza constantemente con el valor de la anterior petición (Señalado en azul).

La idea es que este timestamp se actualice solo cuando el contador se pone a 0

image *Paint genius

Recompilamos, reiniciamos y vemos el log

image

Esto ya tiene mejor pinta, el contador n->count se resetea cada 10 segundos y el valor n->timestamp también. De modo que ahora si, solo bloqueará a aquellas IPs que hagan mas de 20 peticiones en 10 segundos.

Este mismo cambio hay que realizarle otra vez en la comprobación de páginas del sitio visitadas, situado unas pocas lineas mas abajo.

He intentado contactar con el autor de este modulo, para que me verificase que era un fallo y no una mala interpretación mía. Pero el modulo es de hace 4 años así que posiblemente el autor haya cambiado de correo.

mod_evasive es muy conocido y usado, es increíble que nadie haya notado que no funciona como debe o que no se hayan mirado el código fuente, de ahí mis dudas sobre si no estaré equivocado yo. Aunque por otra parte en la configuración por defecto se usa un valor de DOSPageInterval de 1 y entonces la continua actualización de n->timestamp no entorpece el resto de comprobaciones, ya que solo se actualizará cada segundo (no tiene precisión de milisegundos) y si se realizan X peticiones por cada segundo serán detectadas correctamente.

Editado a 02/02/2011 A continuación pongo el parche que ha dejado Antonio en los comentarios:

El parche es este: --- mod_evasive20.c.orig 2005-10-08 21:01:18.000000000 +0200 +++ mod_evasive20.c 2011-02-02 18:00:25.000000000 +0100 @@ -168,9 +168,9 @@ /* Reset our hit count list as necessary */ if (t-n->timestamp>=page_interval) { n->count=0; + n->timestamp = t; } } - n->timestamp = t; n->count++; } else { ntt_insert(hit_list, hash_key, t); @@ -190,9 +190,9 @@ /* Reset our hit count list as necessary */ if (t-n->timestamp>=site_interval) { n->count=0; + n->timestamp = t; } } - n->timestamp = t; n->count++; } else { ntt_insert(hit_list, hash_key, t); lo copiamos y pegamos en un fichero, por ejemplo mod_evasive.patch, dentro del directorio mod_evasive, aplicamos el parche con: patch -p0 < ../mod_evasive.patch

19 comentarios:

  1. Lindo gatito Thor!, nos hacemos un servidor casero? xDDD

    Fea cuando tenga la web rehabilitada (cuando la pague) nos afiliamos? :P

    Saludos!

    ResponderEliminar
  2. Los comentarios son, como es obvio, para comentar sobre el contenido de una entrada.

    Veo que te has enterado de mucho.

    ResponderEliminar
  3. siempre dijimso que nadie mira los codigo fuentes...!

    DSR

    ResponderEliminar
  4. Hola Thor, hace rato que no pasaba por tu blog, es increíble que se les pasara ese "detalle", deberías publicar la versión Fixed by Thor del mod

    Saludos ;-)
    SS

    ResponderEliminar
  5. Yo actualmente tengo un cliente que esta sufriendo un ataque ddos y me disponía a instalarle el mod_evasive pero desde la pagina del autor encontré tu post y mejor decidí esperar.
    De momento instale http://deflate.medialayer.com/ que según leí en foros también funciona, pero ojala que puedas postear la versión corregida del mod_evasive.

    Gracias y saludos

    ResponderEliminar
  6. Venom, creo recordar que era fácil compilarlo, típico make. Prueba a bajártelo cambiar ese código que digo y compilarlo, si tienes algún problema dímelo ;)

    A ver si un día de estos lo vuelvo a mirar y lo dejo por aquí subido ya corregido.

    Un saludo!

    ResponderEliminar
  7. Gracias Thor por responder. Pues intentare compilarlo, la verdad no soy muy hábil con el editor de linux jajaja. De cualquier manera estoy probando una herramienta que se llama ConfigServer Security & Firewall el cual tiene muy buenas criticas en foros de webhosting, para mitigar ataques ddos. La ventaja de ese soft es que para los que tienen cPanel/WHM les es más fácil configurarlo desde las opciones del mismo WHM. Te seguiré visitando. Gracias!

    ResponderEliminar
  8. I think, that You are right. Lastly I reviewed source code for mod_evasive to make my own modifications and it seemed wrong to me to.

    Did You make patch for this bug?

    Or have You got any informations on it form Mr. Zdziarski? I would like to hear from him about it, but he did not respond to my mail.

    I also made some more improvements to mod_evasive like adding IP addresses in CIDR format a.b.c.d/xx with DOSWhitelist or whitelisting based on URI (instead of IP). I will gladly share as soon as I finish and put them on my website.

    ResponderEliminar
  9. I also tried to contact with Mr. Zdziarski but he never asked me.

    The only change that I made was exchange 2 lines as I said in the post.

    Regards!

    ResponderEliminar
  10. Thanks for response :)

    Do You still use mod_evasive? Are You interested in testing my patches? I could send them if You would like and provide me with Your e-mail.

    Oh and I don't speak Your language, so there is not much I could get from Your post except screenshots :) Anyway it seems that it was all I needed to see.

    I'm planning to make continuation of mod_evasive, because many people (including me) seem to make use of it. Yet I didn't see any progress on it on author's page for quite long time.

    ResponderEliminar
  11. Now I haven't a website, so I can't test it.

    It's a good idea continue mod_evasive :D

    ResponderEliminar
  12. si tiene un error y has dado en el clavo, yo tambien estoy en tu situacion, y tambien he comprobado el codigo y es asi como dices.

    ahora estoy viendo como solucionarlo, tengo base de programacion pero no estoy familiarizado a este nivel en linux.

    yo creo que deberias mandar la documentacion directamente a PHP, ya que ellos deberian sacar ya herramientas que ayuden a la defensa, creo que deberian incluir el mod_evasive u otro nuevo en el nuevo PHP.

    ResponderEliminar
  13. El parche es este:
    --- mod_evasive20.c.orig 2005-10-08 21:01:18.000000000 +0200
    +++ mod_evasive20.c 2011-02-02 18:00:25.000000000 +0100
    @@ -168,9 +168,9 @@
    /* Reset our hit count list as necessary */
    if (t-n->timestamp>=page_interval) {
    n->count=0;
    + n->timestamp = t;
    }
    }
    - n->timestamp = t;
    n->count++;
    } else {
    ntt_insert(hit_list, hash_key, t);
    @@ -190,9 +190,9 @@
    /* Reset our hit count list as necessary */
    if (t-n->timestamp>=site_interval) {
    n->count=0;
    + n->timestamp = t;
    }
    }
    - n->timestamp = t;
    n->count++;
    } else {
    ntt_insert(hit_list, hash_key, t);

    lo copiamos y pegamos en un fichero, por ejemplo mod_evasive.patch, dentro del directorio mod_evasive, aplicamos el parche con:

    patch -p0 < ../mod_evasive.patch

    (Bueno, dando la ruta donde tengais el parche)

    ResponderEliminar
  14. Bravo Antonio! Edito la entrada y incluyo el parche mencionandote.

    Muchas gracias!

    ResponderEliminar
  15. server:/usr/lib/apache2/evasive # patch -p0 timestamp>=page_interval) {
    n->count=0;
    + n->timestamp = t;
    }
    }
    - n->timestamp = t;
    n->count++;
    } else {
    ntt_insert(hit_list, hash_key, t);
    @@ -190,9 +190,9 @@
    /* Reset our hit count list as necessary */
    if (t-n->timestamp>=site_interval) {
    n->count=0;
    + n->timestamp = t;
    }
    }
    - n->timestamp = t;
    n->count++;
    } else {
    ntt_insert(hit_list, hash_key, t);

    ResponderEliminar
  16. server:/usr/lib/apache2/evasive # patch -p0 <mod_evasive.patch
    can't find file to patch at input line 4
    Perhaps you used the wrong -p or --strip option?
    The text leading up to this was:
    --------------------------
    |El parche es este:
    |--- mod_evasive20.c.orig 2005-10-08 21:01:18.000000000 +0200
    |+++ mod_evasive20.c 2011-02-02 18:00:25.000000000 +0100
    --------------------------
    File to patch: /usr/lib/apache2/evasive/mod_evasive20.so
    patching file /usr/lib/apache2/evasive/mod_evasive20.so
    patch: **** malformed patch at line 5: /* Reset our hit count list as necessary */

    No me ha dejado aplicar el parche

    ResponderEliminar
  17. Many Thanks Colegas !!

    Feliz Año Nuevo a todos, me fue util.
    La lastima es que eso parece que todavia no se
    haya corrigido, y que muchos sigas bajando la version con el fallo desde la web del autor.

    ResponderEliminar