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.
- 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).
- 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.
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.
Muy interesante....
inedit00 (no verificado)30 Octubre 2010 - 12:22pm
... el artículo, me ha encantado. Hasta ahora yo tan solo tenía en los scripts de iptables la parte de borrar todas las reglas, y las de filtrado.
Muy útil lo de los logs, para debuguear. Aunque yo en los scripts con salida de logging, siempre uso una sintaxis parecida a esto ( en python, por ejemplo )
De modo que puedes poner todos los logs que quieras en el script, y hacer que se guarden o no cambiando el valor de LOG a True o False.
Sobre los módulos, se agradece la explicación de cada uno de ellos.
Un saludo, Jan.
Nota: He escrito este comentario tres veces... definitivamente Control-W en un explorador no te borra una palabra como en bash, si no que te cierra la ventana :P
Muchas gracias
Andy30 Octubre 2010 - 5:33pm
Muchas gracias por el aporte de los logs en python. En bash podría hacerse algo similar, aunque en los scripts de iptables, así como también los de arranque y parada de servicios, para mi el log es mandatorio.
Un saludo,
Andy
Seran cosas mias, pero...
car28 Octubre 2010 - 9:17pm
..el tema en cuestión no trataba de Iptables?? Sin chistes please.
Claro que sí
Sasha30 Octubre 2010 - 2:19pm
Por supuesto, pero de vez en cuando un poco de humor no va mal. Entiendo yo.
De momento estoy guardando todas las entregas para cuando saque tiempo.
Sugerencia
serhost227 Octubre 2010 - 12:15pm
Sugerencia para que digas sobre ipv6: comando ip6tables con misma sintaxis :) (o casi).
A la última fila
Sasha26 Octubre 2010 - 2:36pm
Castigados Admin y Agustín por hablar. A la última fila. Estáis estorbando a todos los que están tomando apuntes, entre ellos ese que se duerme, el del zoidberg ése.
(levantando la mano, para preguntar)
Agustín26 Octubre 2010 - 8:03pm
No está en blanco, profe. Ceci n'est pas une pipe?
Como venga...
Sasha27 Octubre 2010 - 9:19am
Una más y desmonto el bic. Será por hojas para hacer bolillas...
A ver si me entero de la clase, leñe. ¿No hay una opción de ver en modo de impresión, sin comentarios ni ná de eso?
Fácil
admin27 Octubre 2010 - 9:37am
Pulsa el icono de impresora que aparece abajo.
Es pequeño pero funciona ;)
Vale profe
admin26 Octubre 2010 - 3:09pm
Pa'mañana le copiamos 100 veces: "No molestaré a mis compañeros"
Sin copipaste
Sasha26 Octubre 2010 - 3:15pm
Pero sin copipaste.
Ah, y sin programarlo en C o similares, al estilo:
#!/usr/bin/ksh n=0 ; while [ $n -le 100 ]; do echo "No volvere a programar para hacer trampas" let n=n+1 doneHummm... eso lo imprime 101 veces
anv27 Octubre 2010 - 11:15am
Hummm... eso lo imprime 101 veces. Empiezas por cero y entras con "-le 100", o sea con menor o igual que 100. O empiezas por 1 o le pones -lt en lugar de -le.
Susto!!
admin27 Octubre 2010 - 11:23am
Pensé que te referías al icono de "imprimir página" !
interesante pero...
anv26 Octubre 2010 - 2:26pm
Interesante pero tal vez no sea suficientemente didáctico para que quien no conozca esto se decida a apernderlo. Me quedó la sensasión de que parece más difícil de lo que es.
Silencio...
Agustín26 Octubre 2010 - 1:36pm
Uhm... cuanto silencio
admin26 Octubre 2010 - 1:10pm
Supongo que os estáis estudiando el artículo antes de entrar a matar ;)
Dame tiempo
usrdxt26 Octubre 2010 - 2:24pm
y una pantalla decente. En cuanto llegue a casa le pego un vistazo.