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.
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.
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
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…
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…
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
*Paint genius
Recompilamos, reiniciamos y vemos el log
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