Por Andy

En éste primer artículo de la serie, construiremos un script que creará un conjunto de reglas básicas apropiadas para todo equipo Linux, independientemente de la configuración de red del mismo, como ser cantidad o tipo de tarjetas de red, nombre de los dispositivos, direcciones IP, etc. Si bien hay muchos ejemplos de scripts IPTABLES por la red, lo interesante de éste es que se puede llevar a cualquier equipo sin cambiar nada, con lo que cubriremos lo básico con total facilidad...

Este script es apropiado para máquinas con "ip forwarding" deshabilitado. Esto quiere decir que aunque el equipo tenga más de una interfaz de red, sólo utilizará las mismas para enviar y recibir tráfico propio y no para enrutar tráfico de otros equipos entre redes distintas.

También mencionar que sólo se tratará el caso de IPv4, dejando IPv6 como ejercicio para el lector (siempre he querido decir eso).

Para ejecutar IPTABLES o scripts que invoquen a IPTABLES debemos lanzar una shell como root o de lo contrario utilizar "sudo" delante de cada invocación.

Finalmente volver a mencionar que no se explicará aquí la sintaxis de IPTABLES. La misma se puede consultar aquí y está disponible en varios idiomas y formatos.

Comencemos.

 

Inicialización

#!/bin/bash

#####################################################################
# Basic Firewall Script
# Written by Andy
# Published in Kriptopolis.net during October 2010
#####################################################################

#####################################################################
# Initialization Section
#####################################################################

LOGGER="/usr/bin/logger -p kern.info -t FIREWALL"

$LOGGER Initializing

#---- Functions to set the file to one or zero
enable  () { for file in $@; do echo 1 > $file; $LOGGER enabled $file;  done }
disable () { for file in $@; do echo 0 > $file; $LOGGER disabled $file; done }

#---- Binary files location
IPTABLES="/sbin/iptables"

No hay mucho para comentar aquí. Se definen un par de funciones que utilizaremos más adelante con ficheros de "/proc", se asigna una variable para informar de las acciones realizadas en el log del sistema (también se podría haber hecho un alias) y se definen variables para invocar a los ficheros ejecutables.

Lo de definir variables para invocar a los ejecutables tiene varias ventajas:

  • Ahorra al sistema tener que buscar al ejecutable en el PATH.
  • Permite cambiar de ejecutable en caso de preferir otro alternativo (por ejemplo: utilizar "egrep" en vez de "grep") con solo cambiar la asignación de la variable en vez de cambiar cada utilización.
  • Permite adecuar más fácilmente el script a distribuciones que guarden sus ejecutables en otro sitio.
  • Estoy acostumbrado a hacerlo asi en todos mis scripts.

Las rutas de los ejecutables de éste y otros scripts de la presente serie de artículos corresponden a las que se pueden encontrar en distribuciones Debian y derivados. Revisad vuestro sistema y haced los cambios necesarios.

 

Subsistema de red

Al principio del script se configuran algunas directivas del subsistema de red utilizando el filesystem "/proc" y las funciones "enable" y "disable" definidas en la sección anterior. Notar que también se podría haber utilizado el programa "/sbin/sysctl" para lograr el mismo efecto, aunque se hace bastante más difícil al no poder utilizar "*" para cambiar varios valores -desconocidos- de una vez, como debe ser en un script genérico.

#####################################################################
# Network subsystem configuration
#####################################################################

$LOGGER net config start

#---- Disable IP forwarding
disable /proc/sys/net/ipv4/ip_forward

#---- Disable response to broadcasts. You don't want yourself becoming a Smurf amplifier
enable /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts

#---- Enable bad error message protection 
enable /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses 

#---- Turn on reverse path filtering. Safer, but breaks asymetric routing and/or IPSEC
enable /proc/sys/net/ipv4/conf/*/rp_filter

#---- Don't accept source routed packets. Source routing is rarely used for legitimate purposes
disable /proc/sys/net/ipv4/conf/*/accept_source_route

#---- Disable ICMP redirect acceptance which can be used to alter your routing tables
disable /proc/sys/net/ipv4/conf/*/accept_redirects

#---- As we don't accept redirects, don't send Redirect messages either
disable /proc/sys/net/ipv4/conf/*/send_redirects

#---- Ignore packets with impossible addresses
disable /proc/sys/net/ipv4/conf/*/log_martians

#---- Protect against wrapping sequence numbers and aid round trip time measurement
enable /proc/sys/net/ipv4/tcp_timestamps

#---- Help against syn-flood DoS or DDoS attacks using particular choices of initial TCP sequence numbers
enable /proc/sys/net/ipv4/tcp_syncookies

#---- Use Selective ACK which can be used to signify that specific packets are missing
disable /proc/sys/net/ipv4/tcp_sack

$LOGGER net config end

Veamos las directivas una a una.

disable ip_forward
Es para evitar enrutar paquetes entre distintas interfaces de red. Como se mencionó antes, este script está destinado a máquinas sin "ip forwarding".
enable icmp_echo_ignore_broadcasts
Con ésto hacemos que el sistema ignore los pedidos de PING a direcciones de broadcast. Los mismos no tienen sentido y pueden ser utilizados para ataques smurf.
enable icmp_ignore_bogus_error_responses
Esto protege al sistema de errores espúrios que algún atacante puede estar enviándonos. Sin esta directiva, el sistema enviaría a syslog este tipo de errores, generando carga en CPU, disco, posiblemente red y pudiendo llegar al extremo de llenar la partición de log. Con esta directiva estos errores son ignorados silenciosamente.
enable rp_filter
Mediante esta directiva se filtran paquetes que entran por una interfaz, pero con un IP de origen que está enrutado por otra interfaz. El kernel consulta por dónde enrutaría el paquete si el mismo fuese de salida y si la interfaz resultante es distinta a la que está entrando, el paquete se descarta silencionsamente. IPSEC se queja si se utiliza esta directiva sobre interfaces de red físicas.
disable accept_source_route
Aquí le decimos al sistema operativo que no acepte paquetes que indiquen desde origen por dónde se deben enrutar, sino que confiamos en las tablas de routing del sistema operativo.
disable accept_redirects
Esto instruye al sistema para que ignore paquetes ICMP del tipo "redirect". Independientemente de si estos paquetes sean bloqueados o no luego por IPTABLES, el sistema los ignorará y no cambiará sus tablas de routing.
disable send_redirects
Asimismo se pide que el sistema no envíe este tipo de paquetes, que de todos modos sólo deberían enviarlos los routers (y, como ya se ha dicho antes, este script es para sistemas sin "ip forwarding").
disable log_martians
Esta directiva indica que se deben ignorar silenciosamente los paquetes con IPs "imposibles". Hay otra línea de pensamiento según la cual se debe alertar por syslog de la presencia de estos paquetes en la red (utilizando "enable" en vez de "disable"). Sin embargo, considero que ignorarlos al menos es coherente con la recomendación anterior de ignorar los paquetes espúrios. Personalmente prefiero no ver estos paquetes en el log del sistema, especialmente en instalaciones con máquinas Windows mal configuradas ya que a menudo estas se "autoconfiguran" con IPs del rango 169.254.0.0/16 en segmentos de red con otra numeración, haciéndolos candidatos ideales a IPs "imposibles".
enable tcp_timestamps
Mediante esta directiva, se habilita la utilización de timestamps tal y como está documentado en la RFC 1323. Esto ayuda a calcular el tiempo total de ida y vuelta desde que se envía un paquete TCP al servidor remoto y se recibe su correspondiente ACK. Este tiempo es muy importante para determinar con precisión el timeout de retransmisión, evitando retransmisiones innecesarias y aumentando así el ancho de banda percibido. También esto agrega 12 bytes al encabezado TCP, por lo que no se recomienda su utilización en conexiones muy lentas, como por ejemplo vínculos satelitales lentos, leased lines, módems de 56K, etc.
Hay opiniones encontradas sobre la seguridad y la utilidad de los timestamps. Por un lado, tienen la "vulnerabilidad" de que es teóricamente posible descubrir el uptime de una máquina que utilice TCP timestamps. También hay gente que ha reportado problemas con esta opción. Sin embargo, por otro lado se hace prácticamente imprescindible con redes muy rápidas, pero que tengan altas latencias, o que puedan sufrir congestiones o retrasos. Los paquetes TCP de una conexión tienen un número de secuencia de 32 bits para el caso que lleguen a destino desordenados o que lleguen duplicados. Una transmisión sostenida a 1Gbps utilizará todos los números de secuencia en poco más de 30 segundos. Si un paquete se demora debido a una congestión momentánea puede darse el caso de tener 2 paquetes distintos en tránsito con el mismo número de secuencia, algo que hay que evitar por todos los medios. Si se utiliza tcp_timestamps se puede determinar cuál paquete es cuál, evitando confusiones.
enable tcp_syncookies
Con esta directiva el sistema operativo se protege contra un ataque de denegación de servicio consistente en llenar la cola de conexiones semi-abiertas (usualmente unos pocos cientos de entradas, consultar /proc/sys/net/ipv4/tcp_max_syn_backlog en vuestra instalación). El ataque puede hacerse muy fácilmente enviando muchos paquetes SYN, pero no completando el protocolo incial de conexión, lo que llena rápidamente la cola de conexiones semi-abiertas de la máquina atacada. En contraste, el o los atacantes no necesitan guardar ningún tipo de cola o tabla, ya que sólo se dedican a enviar paquetes SYN, posiblemente desde direcciones IP falsas. Si la máquina atacada no está utilizando tcp_syncookies comenzará a rechazar conexiones al llenarse dicha cola, posiblemente rechazando conexiones válidas, denegando el servicio.
Al utilizar tcp_syncookies, una máquina con la cola de conexiones semi-abiertas llena sigue sin poder guardar más conexiones allí, pero en vez de eso envía un paquete de respuesta SYN/ACK especial al equipo que solicita la conexión. Dicho paquete contiene un número de secuencia especial (calculado a partir de los números IP y puertos origen y destino, además de una marca horaria) y si el o los atacantes estaban utilizando IPs falsos nunca lo recibirán ni responderán. Por el contrario, en un intento válido de conexión se recibirá y responderá correctamente este paquete, incluyendo el número de secuencia y completando así el protocolo inicial de conexión (la máquina permite la conexión aunque no haya una entrada en la cola de conexiones semi-abiertas ya que es capaz de recuperar la información que se guardaría allí desde el número de secuencia cuidadosamente calculado).
En todo caso, este mecanismo respeta todos los protocolos (la elección del número de secuencia inicial es responsabilidad de la máquina que recibe el pedido de conexión y puede seleccionar cualquier número, ya sea aleatorio o calculado) y sólo entra en funcionamiento en caso de ataque o sobrecarga, no en situaciones normales.
disable tcp_sack
Esta facilidad, definida en la RFC 2018, se utiliza para aumentar el rendimiento evitando largas retransmisiones ya que permite identificar selectivamente los paquetes que se deben retransmitir (de allí su nombre). Sin embargo, un atacante puede utilizarla para forzar a la máquina víctima a hacer costosas búsquedas dentro de la cola de paquetes en vuelo. Dichas búsquedas pueden consumir bastante CPU, denegando su utilización para otras aplicaciones y clientes en la máquina atacada. También se puede alargar muchísimo el tiempo de transferencia pidiendo constantes retransmisiones, durante todo el cual tendremos un alto consumo de CPU. La máquina del atacante no necesita tener grandes cantidades de memoria ni consumir mucha CPU. Si multiplicamos este tipo de ataques por varios clientes maliciosos, tenemos un ataque distribuído de denegación de servicio en toda regla. Por este motivo se recomienda no activar esta opción.

 

Módulos

En la siguiente sección se cargarán los módulos dinámicos necesarios para el correcto funcionamiento de IPTABLES.

La mayoría de los módulos tienen que ver con el Connection Tracking y ayudan al sistema a relacionar flujos de datos que a veces no tienen relación a simple vista. Por ejemplo: el tráfico FTP se cursa por 2 puertos: el 20 y el 21, uno para datos y el otro para control, que es necesario tratar en conjunto para que las transferencias funcionen correctamente.

#####################################################################
# Dynamic modules section
#####################################################################

$LOGGER module loading start
#----  Load needed modules - comment and uncomment as needed
modprobe nf_conntrack
modprobe nf_conntrack_ipv4
modprobe nf_nat
# modprobe nf_conntrack_ipv6
# modprobe nf_conntrack_amanda
# modprobe nf_nat_amanda
modprobe nf_conntrack_h323
modprobe nf_nat_h323
modprobe nf_conntrack_ftp
modprobe nf_nat_ftp
# modprobe nf_conntrack_netbios_ns
# modprobe nf_conntrack_irc
# modprobe nf_nat_irc
# modprobe nf_conntrack_proto_dccp
# modprobe nf_nat_proto_dccp
modprobe nf_conntrack_netlink
# modprobe nf_conntrack_pptp
# modprobe nf_nat_pptp
# modprobe nf_conntrack_proto_udplite
# modprobe nf_nat_proto_udplite
# modprobe nf_conntrack_proto_gre
# modprobe nf_nat_proto_gre
# modprobe nf_conntrack_proto_sctp
# modprobe nf_nat_proto_sctp
# modprobe nf_conntrack_sane
modprobe nf_conntrack_sip
modprobe nf_nat_sip
# modprobe nf_conntrack_tftp
# modprobe nf_nat_tftp
# modprobe nf_nat_snmp_basic
$LOGGER module loading end

Los módulos que están comentados no son necesarios en muchas de las instalaciones "normales", aunque conviene que los reviséis uno a uno para determinar si debéis cargarlos en las vuestras. Asimismo, quizás haya módulos que aparecen aquí cargados por utilizarlos yo (por ejemplo el de SIP) pero que en vuestra instalación no sean necesarios.

 

Filtrado

Después de inicializar el subsistema de red estamos en condiciones de comenzar con el filtrado. Como antes, procederemos presentando el script por partes y explicando el funcionamiento de cada una.

Recordar que es necesaria cierta familiaridad con el funcionamiento de netfilter y con la sintaxis de IPTABLES, que no se explica aquí. Podéis leer la documentación "oficial" que está disponible en varios idiomas.

 

Definición de reglas - Inicialización

El siguiente trozo de código elimina todos los filtros preexistentes (quita todas las reglas para comenzar con una "hoja en blanco") e inicializa las políticas de cada tabla. Dichas políticas dictaminan qué se hace con un paquete que haya "pasado" por todas las reglas, pero que no haya coincidido con ninguna acción terminal.

Utilizarlo únicamente si sois los únicos administradores del equipo o si os ponéis de acuerdo entre todos los administradores. Este trozo de código borra todo lo definido por IPTABLES, incluso lo que pueda haber definido otro administrador mediante otro script...

#####################################################################
# Filtering Section
#####################################################################

#---- Clear the filter tables. Set default policy to drop
$IPTABLES -t filter -F
$IPTABLES -t filter -X
$IPTABLES -t filter -P INPUT   DROP
$IPTABLES -t filter -P OUTPUT  DROP
$IPTABLES -t filter -P FORWARD DROP
$LOGGER Cleared the filter tables
$LOGGER Set default filter policy to DROP

#---- Clear the nat tables. Set default policy to accept
$IPTABLES -t nat -F
$IPTABLES -t nat -X
$IPTABLES -t nat -P PREROUTING  ACCEPT
$IPTABLES -t nat -P OUTPUT      ACCEPT
$IPTABLES -t nat -P POSTROUTING ACCEPT
$LOGGER Cleared the nat tables
$LOGGER Set default nat policy to ACCEPT

#---- Clear the mangle tables. Set default policy to accept
$IPTABLES -t mangle -F
$IPTABLES -t mangle -X
$IPTABLES -t mangle -P PREROUTING  ACCEPT
$IPTABLES -t mangle -P INPUT       ACCEPT
$IPTABLES -t mangle -P FORWARD     ACCEPT
$IPTABLES -t mangle -P OUTPUT      ACCEPT
$IPTABLES -t mangle -P POSTROUTING ACCEPT
$LOGGER Cleared the mangle tables
$LOGGER Set default mangle policy to ACCEPT

 

Definición de reglas - TCPFLAGS

TCPFLAGS es una cadena que verifica que el campo FLAGS de los paquetes IP sean correctos. Primero veremos la definición de la cadena "aislada" y luego cómo se llega a invocar la misma.

#---- Jump here from TCPFLAGS -- log and drop packets with bad flags
$IPTABLES -N BADFLAGS
$IPTABLES -A BADFLAGS -j LOG --log-level WARNING --log-prefix "IPT TCPFLAGS: "
$IPTABLES -A BADFLAGS -j DROP

#---- Create new chain for checking TCP flags
$IPTABLES -N TCPFLAGS
#---- Log and discard packets with invalid state
$IPTABLES -A TCPFLAGS -p tcp -m state --state INVALID -j LOG --log-level WARNING --log-prefix "IPT INVALID: "
$IPTABLES -A TCPFLAGS -p tcp -m state --state INVALID -j DROP
#---- Discard if first packet on a conversation and no SYN flag
$IPTABLES -A TCPFLAGS -p tcp ! --syn -m state --state NEW -j BADFLAGS
#---- Help to prevent TCP spoofing by sequence number prediction attack
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags SYN,ACK SYN,ACK -m state --state NEW -j REJECT --reject-with tcp-reset
#---- FIN, PSH or URG should always be accompained by ACK
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags ACK,FIN FIN -j BADFLAGS
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags ACK,PSH PSH -j BADFLAGS
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags ACK,URG URG -j BADFLAGS
#---- FIN, SYN and RST are mutually exclusive
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags FIN,RST FIN,RST -j BADFLAGS
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags SYN,FIN SYN,FIN -j BADFLAGS
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags SYN,RST SYN,RST -j BADFLAGS
#---- Packets with no flags are invalid
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags ALL NONE -j BADFLAGS

La primera cadena, BADFLAGS, es invocada por TCPFLAGS cuando encuentra un paquete TCP con el campo FLAGS erróneo. Sólo escribe en el log la aparición del paquete y luego lo descarta silenciosamente (DROP). Si no se desea registrar la aparición éstos tipos de paquetes en el log del sistema os podéis ahorrar completamente la tabla BADFLAGS y reemplazar más abajo cada "-j BADFLAGS" por "-j DROP".

La cadena TCPFLAGS verifica varias combinaciones entre el estado del paquete y el campo FLAGS para asegurar que el paquete IP esté bién formado.

  • Los paquetes con estado INVALID son descartados, previo registro en el log del sistema (no he hecho una cadena separada, como en el caso de BADFLAGS, porque se utiliza una sola vez).
  • Los paquetes nuevos deben venir con el flag SYN activado. En caso contrario descartarlos invocando a BADFLAGS.
  • Hay una clase de ataques de TCP spoofing en el cual el atacante pretende ser nuestro host (envía paquetes utilizando nuestro IP) y envía un paquete SYN a la víctima. La víctima responderá con SYN/ACK como corresponde y si todo va bién (por ejemplo: si la víctima no hace caso de source-routing) el paquete llegará a nuestro host en vez de al atacante. Si no lo respondemos, el atacante puede continuar enviando paqutes y posiblemente comandos maliciosos a la víctima utilizando nuestra dirección IP (aunque sin poder leer sus respuestas). Para ésto deberá ser capaz de predecir el número de secuencia que utiliza la víctima en la respuesta SYN/ACK, cosa posible en algunos casos. Por eso es que si recibimos un paquete SYN/ACK que no sea respuesta a un SYN enviado por nosotros (o sea un paquete con state=NEW) debemos responder a la víctima con TCP RESET cerrando la conexión e impidiendo al atacante enviar más paquetes en nuestro nombre.
  • Otros casos "patológicos" son por ejemplo que los flags FIN, PSH o URG siempre deben ir acompañados del flag ACK, así como también que los flags FIN, SYN y RST son mutuamente excluyentes entre sí, Por último, un paquete con todos los bits del campo FLAGS en 0 también es inválido. Cualquier paquete en éstas condiciones será enviado a BADFLAGS y por lo tanto descartado.

Hasta ahora, TCPFLAGS es como una subrutina de código de programación: inútil si no se invoca desde ningún sitio. El siguiente fragmento de script muestra cómo se llega del procesamiento principal de paquetes a TCPFLAGS.

#---- Perform tcp flags checking for every packet
$IPTABLES -A INPUT  -p tcp -j TCPFLAGS
$IPTABLES -A OUTPUT -p tcp -j TCPFLAGS

Vemos que absolutamente todos los paqutes TCP de entrada y de salida son verificados por TCPFLAGS antes de aceptarse. Esto incluye paquetes que utilicen cualquier interfaz de red (incluída la de loopback) y desde cualquier IP.

También vemos que la invocación a TCPFLAGS es la primera de las reglas de nuestro script IPTABLES. De esta forma nos aseguramos que los paquetes que sobrevivan tendrán el campo FLAGS en órden y no podrán "confundir" a aplicaciones o servicios que los reciban luego. También se analizan los paquetes salientes, lo que obliga a todas las aplicaciones a enviar paquetes "correctos". Esto es algo deseable en la mayoría de los casos, pero puede interferir, por ejemplo, con el uso de herramientas tales como "nmap" que pueden generar paquetes que no se ajusten a este filtrado.

 

Las técnicas utilizadas por TCPFLAGS

En estas pequeñas cadenas hay un par de técnicas dignas de mención:

Separación de verificación y acción
La cadena TCPFLAGS verifica que el paquete sea correcto examinando distintas combinaciones del campo FLAGS. Pero si encuentra que el paquete es inválido transfiere el control a la cadena BADFLAGS, que es la que toma las acciones. Esto también permite que la cadena BADFLAGS sea invocada desde varios sitios, ya que no analiza el estado del paquete sino que simplemente toma acciones.
Evitar múltiples verificaciones
La cadena BADFLAGS hace varias cosas: enviar un mensaje al log del sistema y descartar el paquete. Tener las acciones separadas permite verificar el estado del paquete una sola vez, evitando múltiples verificaciones idénticas.

Consideremos el siguiente código:

#---- Code extract from the TCPFLAGS sample above
$IPTABLES -A TCPFLAGS -p tcp ! --syn -m state --state NEW -j BADFLAGS

#---- Alternative rules with no BADFLAGS chain
$IPTABLES -A TCPFLAGS -p tcp ! --syn -m state --state NEW -j LOG --log-level WARNING --log-prefix "IPT TCPFLAGS: "
$IPTABLES -A TCPFLAGS -p tcp ! --syn -m state --state NEW -j DROP

Las últimas 2 reglas hacen lo mismo que la primera sin necesidad de definir la cadena BADGLAGS. Pero tiene que verificar 2 veces el estado del paquete haciendo 2 comparaciones idénticas. Los paquetes que no cumplan la condición (por ejemplo los paquetes bien formados) deberán transitar por 2 reglas antes de ser aceptados en vez de sólo 1 en el caso de la primera regla.

 

Definición de reglas - Sección de Allow (permitir)

Lo siguiente es permitir algunos paquetes "genéricos" que son necesarios para el funcionamiento de cualquier sistema.

#---- Allow anything incomming over loopback
$IPTABLES -A INPUT -i lo -j ACCEPT
$LOGGER Allowed anything incomming over the loopback interface

#---- Allow outgoing packets generated in this machine
$IPTABLES -A OUTPUT -p tcp  -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A OUTPUT -p udp  -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A OUTPUT -p icmp -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

#---- Allow ongoing conversations (stateful filtering)
$IPTABLES -A INPUT -p tcp  -m state --state RELATED,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -p udp  -m state --state RELATED,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -p icmp -m state --state RELATED,ESTABLISHED -j ACCEPT

A cualquier paquete que intente llegar a la interfaz de loopback (lo) se le permite hacerlo. Esto es necesario para la comunicación interna entre los distintos daemons y procesos del sistema. Tener en cuenta que para poder llegar a la interfaz de loopback el paquete debe ser generado internamente. No se puede llegar a lo desde el exterior.

Acto seguido, se permite la salida de cualquier paquete ICMP, TCP y UDP generado por el sistema (state=NEW) y también "conversaciones" TCP/IP en curso (con state=ESTABLISHED o state=RELATED).

Para el caso de la entrada, se permiten únicamente las respuestas a conversaciones ya en curso (stateful filtering) y no intentos de conexión nuevos... por ahora.

Entre las reglas anteriores de INPUT y de OUTPUT ya hemos cubierto todos los casos de filtrado genérico. De ahora en adelante, para cualquier conexión entrante que querramos aceptar bastará con permitir el paso del primer paquete (state=NEW) mediante el cual se establecerá la conversación y luego las reglas anteriores permitirán que la misma continúe sin inconvenientes.

 

Definición de reglas - Aceptación de servicios

#---- Accept (and answer) PING requests, but no faster than 1 per second. Comment if not needed
$IPTABLES -A INPUT -p icmp -m state --state NEW --icmp-type echo-request -m limit --limit 1/s --limit-burst 1 -j ACCEPT

Esta regla permite la llegada de nuevos paquetes ICMP del tipo "echo-request" (ping), lo que provoca la generación y envío automático de paquetes ICMP del tipo "echo-response". Aunque dichos paquetes salientes son de una clase distinta, se consideran RELATED y se permiten por las reglas anteriores. Los paquetes ICMP entrantes se limitan aquí a 1 por segundo y los demás se descartan de forma implícita por la política general de DROP. Por supuesto, lo de la limitación es opcional y se pueden cambiar sus valores límite o directamente se puede eliminar, permitiendo la entrada de ping a cualquier ritmo.

Al no mencionar ninguna placa de red o dirección IP, se aceptan paquetes de ping provenientes desde cualquier origen y entrando por cualquier interfaz de red. Agregando los parámetros adecuados se puede restringir por interfaz de red de entrada (-i), por dirección IP de origen (-s), dirección IP de destino (-d), etc.

Para aceptar cualquier servicio TCP se puede proceder como en el ejemplo siguiente, correspondiente a SSH. Para ello, se crea una cadena para el tratamiento de paquetes entrantes SSH. También aquí se utiliza limitación de nuevas conexiones por unidad de tiempo. En mi caso, lo estoy limitando a 3 nuevos intentos de conexión por minuto lo cual es muy poco para algunas instalaciones, pero adecuado para mi humilde máquina personal. Si se intenta más rápido, se descartan los paquetes silencionsamente. Dicha limitación no afecta a los paquetes pertenecientes a sesiones ya establecidas, sólo a los intentos de abrir conexiones nuevas.

#---- Create a new chain for SSH packets
$IPTABLES -N SSH
#---- Allow only 3 new incoming ssh connections per minute
$IPTABLES -A SSH -m limit --limit 3/minute --limit-burst 1 -j ACCEPT
#---- Log and drop faster connection attempts
$IPTABLES -A SSH -j LOG --log-level WARNING --log-prefix "IPT SSH connection too fast: "
$IPTABLES -A SSH -j DROP
#---- Allow incoming ssh
$IPTABLES -A INPUT -p tcp --dport 22 -m state --state NEW -j SSH

Tener muchísimo cuidado con limitaciones como esta, ya que pueden conducir a una denegación de servicio (DoS) accidental. Por ejemplo: si alguien intenta acceder por SSH contínuamente (pongamos 1 vez por segundo), se descartarán paquetes (y por ende, intentos de conexión) por la limitación de 3 por minuto. No sólo se descartarán los del atacante, sino también los míos, y me será muy difícil establecer una conexión. Utilizar "recent" en vez de "limit" ayuda si nos están bombardenado desde un mismo IP, pero no en caso de un ataque de denegación de servicio distribuído (DDoS). Además "recent" es más difícil de usar.

En caso que no se necesite o no se desee tener PING o acceso SSH, se pueden comentar u omitir las reglas anteriores sin afectar al resto del script.

Si se necesita abrir algún otro puerto (por ejemplo: puerto TCP 80 si estamos ejecutando un servidor Apache en nuestro equipo) y no interesa limitar la cantidad de conexiones o tener una línea de log personalizada para el mismo, se pueden agregar reglas más simples:

#---- Feel free to open any other needed incoming port
#$IPTABLES -A INPUT -p [tcp|udp] --dport YOUR_PORT -m state --state NEW -j ACCEPT

 

Definición de reglas - Final

Una vez que se han permitido todos los puertos deseados, las últimas relgas son de registrar que un paquete ha pasado por todas las cadenas y no ha sido aceptado por ninguna, por lo que se descartará (recordar que la política por defecto era descartar).

#---- Log (and drop by default) any other packet
$IPTABLES -A INPUT   -j LOG --log-level WARNING --log-prefix "IPT INPUT packet died: "
$IPTABLES -A OUTPUT  -j LOG --log-level WARNING --log-prefix "IPT OUTPUT packet died: "
$IPTABLES -A FORWARD -j LOG --log-level WARNING --log-prefix "IPT FORWARD packet died: "
$LOGGER Log and drop any other packet

exit 0

 

Script completo

El script básico completo queda entonces:

#!/bin/bash

#####################################################################
# Basic Firewall Script
# Written by Andy
# Published in Kriptopolis.net during October 2010
#####################################################################

#####################################################################
# Initialization Section
#####################################################################

LOGGER="/usr/bin/logger -p kern.info -t FIREWALL"

$LOGGER Initializing

#---- Functions to set the file to one or zero
enable  () { for file in $@; do echo 1 > $file; $LOGGER enabled $file;  done }
disable () { for file in $@; do echo 0 > $file; $LOGGER disabled $file; done }

#---- Binary files location
IPTABLES="/sbin/iptables"

#####################################################################
# Network subsystem configuration
#####################################################################

$LOGGER net config start

#---- Disable IP forwarding
disable /proc/sys/net/ipv4/ip_forward

#---- Disable response to broadcasts. You don't want yourself becoming a Smurf amplifier
enable /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts

#---- Enable bad error message protection 
enable /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses 

#---- Turn on reverse path filtering. Safer, but breaks asymetric routing and/or IPSEC
enable /proc/sys/net/ipv4/conf/*/rp_filter

#---- Don't accept source routed packets. Source routing is rarely used for legitimate purposes
disable /proc/sys/net/ipv4/conf/*/accept_source_route

#---- Disable ICMP redirect acceptance which can be used to alter your routing tables
disable /proc/sys/net/ipv4/conf/*/accept_redirects

#---- As we don't accept redirects, don't send Redirect messages either
disable /proc/sys/net/ipv4/conf/*/send_redirects

#---- Ignore packets with impossible addresses
disable /proc/sys/net/ipv4/conf/*/log_martians

#---- Protect against wrapping sequence numbers and aid round trip time measurement
enable /proc/sys/net/ipv4/tcp_timestamps

#---- Help against syn-flood DoS or DDoS attacks using particular choices of initial TCP sequence numbers
enable /proc/sys/net/ipv4/tcp_syncookies

#---- Use Selective ACK which can be used to signify that specific packets are missing
disable /proc/sys/net/ipv4/tcp_sack

$LOGGER net config end

#####################################################################
# Dynamic modules section
#####################################################################

$LOGGER module loading start
#----  Load needed modules - comment and uncomment as needed
modprobe nf_conntrack
modprobe nf_conntrack_ipv4
modprobe nf_nat
# modprobe nf_conntrack_ipv6
# modprobe nf_conntrack_amanda
# modprobe nf_nat_amanda
modprobe nf_conntrack_h323
modprobe nf_nat_h323
modprobe nf_conntrack_ftp
modprobe nf_nat_ftp
# modprobe nf_conntrack_netbios_ns
# modprobe nf_conntrack_irc
# modprobe nf_nat_irc
# modprobe nf_conntrack_proto_dccp
# modprobe nf_nat_proto_dccp
modprobe nf_conntrack_netlink
# modprobe nf_conntrack_pptp
# modprobe nf_nat_pptp
# modprobe nf_conntrack_proto_udplite
# modprobe nf_nat_proto_udplite
# modprobe nf_conntrack_proto_gre
# modprobe nf_nat_proto_gre
# modprobe nf_conntrack_proto_sctp
# modprobe nf_nat_proto_sctp
# modprobe nf_conntrack_sane
modprobe nf_conntrack_sip
modprobe nf_nat_sip
# modprobe nf_conntrack_tftp
# modprobe nf_nat_tftp
# modprobe nf_nat_snmp_basic
$LOGGER module loading end

#####################################################################
# Filtering Section
#####################################################################

#---- Clear the filter tables. Set default policy to drop
$IPTABLES -t filter -F
$IPTABLES -t filter -X
$IPTABLES -t filter -P INPUT   DROP
$IPTABLES -t filter -P OUTPUT  DROP
$IPTABLES -t filter -P FORWARD DROP
$LOGGER Cleared the filter tables
$LOGGER Set default filter policy to DROP

#---- Clear the nat tables. Set default policy to accept
$IPTABLES -t nat -F
$IPTABLES -t nat -X
$IPTABLES -t nat -P PREROUTING  ACCEPT
$IPTABLES -t nat -P OUTPUT      ACCEPT
$IPTABLES -t nat -P POSTROUTING ACCEPT
$LOGGER Cleared the nat tables
$LOGGER Set default nat policy to ACCEPT

#---- Clear the mangle tables. Set default policy to accept
$IPTABLES -t mangle -F
$IPTABLES -t mangle -X
$IPTABLES -t mangle -P PREROUTING  ACCEPT
$IPTABLES -t mangle -P INPUT       ACCEPT
$IPTABLES -t mangle -P FORWARD     ACCEPT
$IPTABLES -t mangle -P OUTPUT      ACCEPT
$IPTABLES -t mangle -P POSTROUTING ACCEPT
$LOGGER Cleared the mangle tables
$LOGGER Set default mangle policy to ACCEPT

#---- Jump here from TCPFLAGS -- log and drop packets with bad flags
$IPTABLES -N BADFLAGS
$IPTABLES -A BADFLAGS -j LOG --log-level WARNING --log-prefix "IPT TCPFLAGS: "
$IPTABLES -A BADFLAGS -j DROP

#---- Create new chain for checking TCP flags
$IPTABLES -N TCPFLAGS
#---- Log and discard packets with invalid state
$IPTABLES -A TCPFLAGS -p tcp -m state --state INVALID -j LOG --log-level WARNING --log-prefix "IPT INVALID: "
$IPTABLES -A TCPFLAGS -p tcp -m state --state INVALID -j DROP
#---- Discard if first packet on a conversation and no SYN flag
$IPTABLES -A TCPFLAGS -p tcp ! --syn -m state --state NEW -j BADFLAGS
#---- Help to prevent TCP spoofing by sequence number prediction attack
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags SYN,ACK SYN,ACK -m state --state NEW -j REJECT --reject-with tcp-reset
#---- FIN, PSH or URG should always be accompained by ACK
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags ACK,FIN FIN -j BADFLAGS
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags ACK,PSH PSH -j BADFLAGS
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags ACK,URG URG -j BADFLAGS
#---- FIN, SYN and RST are mutually exclusive
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags FIN,RST FIN,RST -j BADFLAGS
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags SYN,FIN SYN,FIN -j BADFLAGS
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags SYN,RST SYN,RST -j BADFLAGS
#---- Packets with no flags are invalid
$IPTABLES -A TCPFLAGS -p tcp --tcp-flags ALL NONE -j BADFLAGS

#---- Perform tcp flags checking for every packet
$IPTABLES -A INPUT  -p tcp -j TCPFLAGS
$IPTABLES -A OUTPUT -p tcp -j TCPFLAGS

#---- Allow anything incomming over loopback
$IPTABLES -A INPUT -i lo -j ACCEPT
$LOGGER Allowed anything incomming over the loopback interface

#---- Allow outgoing packets generated in this machine
$IPTABLES -A OUTPUT -p tcp  -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A OUTPUT -p udp  -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A OUTPUT -p icmp -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

#---- Allow ongoing conversations (stateful filtering)
$IPTABLES -A INPUT -p tcp  -m state --state RELATED,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -p udp  -m state --state RELATED,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -p icmp -m state --state RELATED,ESTABLISHED -j ACCEPT

#---- Accept (and answer) PING requests, but no faster than 1 per second. Comment if not needed
$IPTABLES -A INPUT -p icmp -m state --state NEW --icmp-type echo-request -m limit --limit 1/s --limit-burst 1 -j ACCEPT

#---- Create a new chain for SSH packets
$IPTABLES -N SSH
#---- Allow only 3 new incoming ssh connections per minute
$IPTABLES -A SSH -m limit --limit 3/minute --limit-burst 1 -j ACCEPT
#---- Log and drop faster connection attempts
$IPTABLES -A SSH -j LOG --log-level WARNING --log-prefix "IPT SSH connection too fast: "
$IPTABLES -A SSH -j DROP
#---- Allow incoming ssh
$IPTABLES -A INPUT -p tcp --dport 22 -m state --state NEW -j SSH

#---- Feel free to open any other needed incoming port
#$IPTABLES -A INPUT -p [tcp|udp] --dport YOUR_PORT -m state --state NEW -j ACCEPT

#---- Log (and drop by default) any other packet
$IPTABLES -A INPUT   -j LOG --log-level WARNING --log-prefix "IPT INPUT packet died: "
$IPTABLES -A OUTPUT  -j LOG --log-level WARNING --log-prefix "IPT OUTPUT packet died: "
$IPTABLES -A FORWARD -j LOG --log-level WARNING --log-prefix "IPT FORWARD packet died: "
$LOGGER Log and drop any other packet

exit 0

Dicho script, aunque básico, es genérico y funcional, lo que significa que se puede utilizar en cualquier instalación Linux con cualquier número o tipo de interfaces de red.

Para hacer pruebas con el mismo, os recomiendo, al menos al principio, utilizar algún PC al que tengáis acceso local (por consola), ya que si tenéis algún error en el script puede ser que perdáis acceso remoto por red.

Si os véis obligados a probar en forma remota, entonces una cosa que podéis hacer es tener a mano un pequeño script sólo con la parte de inicialización del script de filtrado, a fin de "limpiar" todas las cadenas de todas las tablas y dejar el sistema sin ninguna regla de IPTABLES. Sería un script como el siguiente:

#!/bin/bash

#####################################################################
# Script to clear all chains from all tables
# Written by Andy
# Published in Kriptopolis.net during October 2010
#####################################################################

LOGGER="/usr/bin/logger -p kern.info -t FIREWALL"

#---- Clear the filter tables. Set default policy to drop
$IPTABLES -t filter -F
$IPTABLES -t filter -X
$IPTABLES -t filter -P INPUT   DROP
$IPTABLES -t filter -P OUTPUT  DROP
$IPTABLES -t filter -P FORWARD DROP
$LOGGER Cleared the filter tables
$LOGGER Set default filter policy to DROP

#---- Clear the nat tables. Set default policy to accept
$IPTABLES -t nat -F
$IPTABLES -t nat -X
$IPTABLES -t nat -P PREROUTING  ACCEPT
$IPTABLES -t nat -P OUTPUT      ACCEPT
$IPTABLES -t nat -P POSTROUTING ACCEPT
$LOGGER Cleared the nat tables
$LOGGER Set default nat policy to ACCEPT

#---- Clear the mangle tables. Set default policy to accept
$IPTABLES -t mangle -F
$IPTABLES -t mangle -X
$IPTABLES -t mangle -P PREROUTING  ACCEPT
$IPTABLES -t mangle -P INPUT       ACCEPT
$IPTABLES -t mangle -P FORWARD     ACCEPT
$IPTABLES -t mangle -P OUTPUT      ACCEPT
$IPTABLES -t mangle -P POSTROUTING ACCEPT
$LOGGER Cleared the mangle tables
$LOGGER Set default mangle policy to ACCEPT

exit 0

Si guardáis el script como "cleariptables.sh", por ejemplo, entonces podéis ejecutar "(sleep 300; /path/to/script/cleariptables.sh)&" justo antes de ejecutar el script de filtrado. De esa forma sabréis que al cabo de 5 minutos se eliminarán todas las reglas de IPTABLES y si habíais perdido acceso al equipo por un error en el script de filtrado, podréis volver a conectaros. Por otro lado, os deja sólo 5 minutos para hacer pruebas.

Recordar que es necesario ser root o utilizar "sudo" para poder ejecutar IPTABLES o scripts que invoquen a IPTABLES.

Una vez terminadas las pruebas, si queremos que nuestro equipo esté contínuamente protegido por las reglas de IPTABLES, habrá que ejecutar el script durante el arranque del sistema, utilizando el mecanismo de autoejecución que proporcione el mismo. Consultad la documentación de vuestra distribución.

Anterior | Siguiente

En los próximos artículos veremos cómo extender y mejorar el script de filtrado para que sea algo más que un conjunto de reglas estáticas.