Defendiendo la disponibilidad de un servicio con iptables

La famosa sigla CID define el trípode en el cual se sustenta la seguridad informática: Confidencialidad, Integridad, Disposnibilidad.

Es decir, al interrumpir la disponibilidad de un servicio se quiebra la seguridad de uno o más servicios.

El famoso ataque de denegación de servicio puede colgar aplicaciones de un servidor o inundarlos de tráfico afectando seriamente el rendimiento. Muchas veces ese tipo de ataque proviene de direcciones IP falsificadas y desde más de un origen. Existen variantes de este tipo de agresiones que pueden incluso aprovechar vulnerabilidades de los dispositivos víctima, de manera tal que los atacantes pueden por ejemplo hacer reemplazar el firmware original por otro malicioso.

El módulo xt_recent

Como vimos, bloquear o limitar el tráfico de una lista estática de direcciones IP no es conveniente. Exige en cambio adoptar una defensa dinámica y proactiva.

El módulo de iptables xt_recent es una de las opciones a las que podemos recurrir en estos casos. Fue creado por Stephen Frost en 2003^1   y lo mantiene actualmente el equipo de iptables.^2

El presente artículo intenta mostrar de manera sencilla y clara el funcionamiento del módulo, no es un curso de iptables ni una receta para diseñar una política de firewall. El propósito es mostrar como el propio kernel posee instrumentos para defenderse contra un (D)DOS.

Como funciona y usos

El modulo xt_recent o más comúnmente conocido recent crea una conjunto de direcciones IPs de acuerdo a ciertos criterios que queramos establecer. Por ejemplo podríamos crear un regla que coincida con paquetes de tipo icmp pertenecientes a conexiones nuevas.

Es cierto, tal vez no podamos hablar estrictamente de conexiones nuevas para este tipo de protocolo. Pero recordemos que iptables es un firewall de estado es decir, no solamente analiza y/o filtra paquetes de manera aislada. Sino que además puede examinar su historial: saber si un paquete pertenece a una conexión nueva (NEW), establecida (ESTABLISHED), relacionada (RELATED) o no válida (INVALID).

Por ejemplo al realizar un echo-request con ping y recibir desde el otro host un echo-reply, iptables, o mejor dicho, netfilter considera que se estableció una conexión.

Si queremos contrarrestar un icmp-flood de manera efectiva lo correcto es limitar las paquetes nuevos.

Reglas sencillas para probarlo

Las siguientes reglas permiten aplicar un límite de un paquete nuevo icmp por minuto (es un ejemplo extremo, pero útil para explicar el funcionamiento del módulo)

iptables -A INPUT -p icmp -m state --state NEW -m recent --update --seconds 60 --hitcount 1 --name icmpflood -j DROP
iptables -A INPUT -p icmp -m state --state NEW -m recent --set --name icmpflood

Como siempre es importante el orden de las reglas, podríamos invertirlas pero en la documentación de primera mano^1 está en ese orden (y las cosas cambiarían, podés probarlo vos el resultado).

Pero veamos las cosas tal como están. Supongamos que no han llegado paquetes nuevos icmp: Al arribar uno, no coincidirá con la primer regla. Explicaremos la razón:

El parámetro --seconds-- indica el intervalo de tiempo que examinaremos, mientras que --hitcount evalúa la cantidad de paquetes de este tipo desde la misma dirección. Es importante entender que esta regla no incrementa el contador. Solamente examina cantidad de paquetes por unidad de tiempo. Como hasta ahora el contador está en 0, el paquete podrá ingresar (asumimos que la política predeterminada es ACCEPT, lo cual puede – en principio – ser inseguro, pero nuestro propósito es explicar el funcionamiento de recent, de manera que el lector está advertido). Insistimos: esta regla NO penaliza, es decir no incrementa el contador. ¿Quedó claro?

Vayamos a la segunda regla: esta regla crea un conjunto llamado icmpflood e incrementa el contador para los paquetes que cumplan con los requisitos estipulados, es decir: con protocolo icmp y estado NEW. De manera que al llegar el primer paquete se agrega la dirección IP al set icmpflood y el número de hits es ahora 1.

Recién al llegar el segundo paquete en menos de 60 segundos se habrán cumplidos los requisitos de la primer regla y por lo tanto el paquete será descartado.

Método sencillo para testear las reglas

Lo podemos probar sencillamente con los siguientes comandos desde otro host:

pingwait(){ ping -q -c1 $1 & sleep 31; }
IP=192.168.80.148
pingwait $IP; pingwait $IP; ping -q -c1 192.168.80.148

Es decir lo que hacemos es enviar dos paquetes nuevos icmp cada 31 segundos.

El resultado en el host de destino es:

Every 2,0s: iptables -L INPUT -vn  

Chain INPUT (policy ACCEPT 63 packets, 3612 bytes)
 pkts bytes target     prot opt in     out     source               destination
    2   168 DROP       icmp --  *      *       0.0.0.0/0            0.0.0.0/0            state NEW recent: UPDATE seconds:
 60 hit_count: 1 name: icmpflood side: source mask: 255.255.255.255
    1    84            icmp --  *      *       0.0.0.0/0            0.0.0.0/0            state NEW recent: SET name: icmpflood side: source mask: 255.255.255.255

Explicación amigable de rcheck y update

No explicamos en absoluto el significado de UPDATE (–update) hasta ahora y la diferencia con CHECK (–rcheck). No es sorpresa que la mejor explicación esté en la documentación original. Muchos de los sitios que intentan explicarlo en mi modesta opinión no lo hacen de la manera más clara. Así que trataré de hacerme entender, ya que son dos parámetros claves. Pero antes de hacerlo y sin querer eludir la explicación, mostraré el resultado al cambiar UPDATE por CHECK:

iptables -F && iptables -X && iptables -Z
iptables -A INPUT -p icmp -m state --state NEW -m recent --rcheck --seconds 60 --hitcount 1 --name icmpflood -j DROP
iptables -A INPUT -p icmp -m state --state NEW -m recent --set --name icmpflood
Every 2,0s: iptables -L INPUT -vn                                                                       Thu Jan  7 16:49:41 2016

Chain INPUT (policy ACCEPT 57 packets, 3250 bytes)
 pkts bytes target     prot opt in     out     source               destination
    1    84 DROP       icmp --  *      *       0.0.0.0/0            0.0.0.0/0            state NEW recent: CHECK seconds:
60 hit_count: 1 name: icmpflood side: source mask: 255.255.255.255
    2   168            icmp --  *      *       0.0.0.0/0            0.0.0.0/0            state NEW recent: SET name: icmpf
lood side: source mask: 255.255.255.255

Supongamos que tenés una bolsa con 10 caramelos y un nene te pide que se los des. Aceptás con una condición, solamente podrá comer 2 por hora. Entonces, podrías adoptar dos políticas con el pequeño:

Laxa (–rcheck)

  • Hora 15:00 Viene el nene por primera vez y te pide 2 caramelos. Le das un caramelo.
  • Hora 15:15: ¡Regresa el niño en busca de más caramelos! Está ok, le regalás el dulce, pero le decís que es el último durante esa hora.
  • Hora 15:30: Otra vez el simpático nene te pido caramelos, le explicás que ya comió los dos caramelos que corresponden a esa hora, recién a las 16:00 se lo darás.
  • Hora 16:00: Ahora sí tal lo prometido le podrás dar 1 o 2 caramelos durante las 16:00 hasta las 16:59.

Rígida (–update)

  • Hora 15:00 Viene el nene por primera vez y te pide 2 caramelos. Le das un caramelo.
  • Hora 15:15: ¡Regresa el niño en busca de más caramelos! Está ok, le regalás el dulce, pero le decís que es el último durante esa hora.
  • Hora 15:30: Otra vez el simpático nene te pido caramelos, le explicás que ya comió los dos caramelos que corresponden a esa hora… Pero que durante una hora a partir de ahora no le podrás dar más caramelos.
  • Hora 15:45: El chico insiste quiere más caramelos. Como disciplina le decís que por no esperás volvés a extender el tiempo de espera. De manera que tendrá que esperar hasta las 16.45 para recibir otro caramelo.
  • Hora 16:00 El pobre nene insiste, entonces extendés la penalidad una hora más. Y el nino recién ahí comprende la indicación y espera una hora.
  • Hora 17:00: Ahora sí tal lo prometido le podrás dar 1 o 2 caramelos durante los siguientes 60 minutos.

Así que esas son las diferencias entre --rcheck, --update, cuanto menos respete el atacante el tiempo estipulado en --seconds peor será le penalidad. En el ejemplo que estuvimos viendo --update significa que el otro host deberá esperar sí o sí 1 minuto entre ping y ping. En cambio --rcheck es más permisivo, por cada nuevo minuto nuevo que comience podrá enviar la cantidad estipulada en --hitcounts que en nuestro ejemplo fue 1 paquete.

Archivos de listas en /proc/net/xt_recent

root@raspberrypi:~#  cat  /proc/net/xt_recent/icmpflood src=192.168.80.250 ttl: 64 last_seen: 29694939 oldest_pkt: 3 29688739, 29691839, 29694939

Hay un archivo por lista (set). Vemos en el archivo /proc/net/xt_recent/icmpflood la dirección de origen del paquete y además la última vez que fue vista usando la unidad de tiempo jiffies.

Parámetros del kernel

Además se le pueden pasar parámetros al módulo

ip_list_tot: Número de direcciones IPs que puede recordar por lista
ip_pkt_list_tot: Número de paquetes que puede recordar por IP (el máximo es 255)

Conclusión

Como vemos si bien existen otros métodos para mitigar un DDOS o DOS, este tiene la ventaja de usar directamente netfilter, sin la necesidad de instalar una herramienta adicional.