Configuración de un firewall con Iptables (I)

Buenas a todos.

Este es el primero de dos artículos, en el que vamos a mostrar como establecer una configuración básica de un firewall utilizando IPtables. Vamos a elegir para esta práctica un escenario real: la red del departamento de informática del instituto donde estudié ASIR. En la siguiente parte trasladaremos todo lo que vamos a ver en este post a un escenario empresarial, añadiendo complejidad y más ejemplos.

El escenario se va a recrear utilizando máquinas virtuales:

IMPORTANTE

Para poder ver el efecto de cada regla y comprobar el correcto funcionamiento de las mismas, es necesario recrear el escenario propuesto. Para ello, puedes dirigirte al siguiente enlace:

Despliegue automatizado del escenario con Vagrant

En el artículo que acabos de indicar, explicamos cómo podemos desplegar este entorno automáticamente y en cualquier lugar con Vagrant, proporcionando los medios necesarios e indicando los requisitos.

En este artículo en el que nos encontramos veremos:

  • Teoría básica sobre iptables
  • Cómo definir una política por defecto para el firewall
  • Cómo hacer persistente la configuración del firewall
  • Cómo borrar parcial o totalmente la configuración del firewall
  • Objetivos de nuestro firewall y reglas para conseguirlos
  • Automatización de la configuración del firewall desarrollada en el punto anterior

UN POCO DE TEORÍA

No vamos a detenernos mucho en explicar el funcionamiento de iptables, ni en la cantidad de opciones y granularidad que ofrece a la hora de definir reglas, pero si vamos a ver por encima los conceptos más importantes.

Tablas

iptables cuenta con cinco tablas, que son zonas en las que una cadena de reglas se puede aplicar:

  1. raw filtra los paquetes antes que cualquier otra tabla. Se utiliza principalmente para configurar exenciones de seguimiento de conexiones en combinación con el target NOTRACK.
  2. filter es la tabla por defecto (si no se pasa la opción -t).
  3. nat se utiliza para la traducción de dirección de red (por ejemplo, el redirección de puertos). Debido a las limitaciones en iptables, el filtrado no se debe hacer aquí.
  4. mangle se utiliza para la alteración de los paquetes de red especializados (véase Mangles packet).
  5. security se utiliza para reglas de conexión de red Mandatory Access Control.

Cadenas

Las tablas contienen cadenas, que son listas de reglas que ordenan los paquete de red. Por defecto, la tabla filter contiene tres cadenas integradas: INPUT, OUTPUT y FORWARD.

  1. Todo el tráfico entrante, dirigido a la máquina, se hace pasar a través de la cadena INPUT.
  2. Todo el tráfico saliente, generado localmente, pasa a través de la cadena OUTPUT
  3. Todo el tráfico enrutado, que no se ha suministrado localmente, pasa a través de la cadena FORWARD.

El usuario puede definir las reglas de las cadenas para hacerlas más eficientes.

Las cadenas compiladas tienen un target predefinido, que se utiliza si no hay reglas definidas. Ni las cadenas compiladas ni las definidas por el usuario pueden ser un target predefinido.

Reglas

El filtrado de los paquetes de red se basa en rules -reglas-, que se especifican por diversos matches -«coincidencias»- (condiciones que el paquete debe satisfacer para que la regla se puede aplicar), y un target -«objetivo» (acción a tomar cuando el paquete coincide con la condición plenamente). Si bien las condiciones individuales suelen ser muy simples, la especificación de la regla completa puede ser muy compleja.

Los targets se especifican mediante la opción -j o –jump. Los targets pueden ser tanto las cadenas definidas por el usuario, como uno de los targets integrados especiales, o una extensión de target. Los targets integrados son ACCEPT, DROP, QUEUE y RETURN; las extensiones de target son, por ejemplo, REJECT y LOG. Si el target es un target integrado, el destino del paquete es decidido inmediatamente y el procesamiento del paquete red en la tabla actual se detiene. Si el target es una cadena definida por el usuario y el paquete supera con éxito esta segunda cadena, se moverá a la siguiente regla de la cadena inicial. Las extensiones de target pueden ser tanto terminating (como los targets integrados) como non-terminating (como las cadenas especificadas por el usuario), Véase iptables-extensions(8) para obtener más detalles

Módulos

Hay muchos módulos que pueden ser utilizados para reforzar iptables, como connlimit, conntrack, limit y recent. Estos módulos añaden funcionalidad extra al permitir reglas de filtrado avanzadas.

Configuración del firewall

Antes que nada, se debe establecer una política por defecto.

Ésta puede ser de dos tipos:

ACCEPT –> Acepta cualquier tráfico por defecto. Al establecer esta política por defecto, debemos denegar el tráfico que nos interese, y se permitirá todo lo demás. Un firewall con esta política es más fácil de configurar pero menos seguro.

DROP –> Deniega cualquier tráfico por defecto. Al establecer esta política por defecto, debemos permitir el tráfico que nos interese, y se denegará todo lo demás. Un firewall con esta política es más difícil de configurar pero más seguro.

En esta práctica estableceremos una política por defecto DROP, de la siguiente manera:

iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

Lo siguiente que debemos saber es cómo hacer persistente la configuración de nuestro firewall, ya que, si no la hacemos permanente, las reglas que introduzcamos se borrarán al reiniciarse el servidor. Para ello, debemos instalar el paquete iptables-persistent, de la siguiente manera:

apt install iptables-persistent

Una vez instalado el paquete, tenemos dos comandos:

netfilter-persistent save #Hace permanente el conjunto de reglas que haya en ese momento.
netfilter-persistent restore #Recupera el conjunto de reglas guardadas.

También debemos saber como eliminar las reglas existentes. Esto se hace de la siguiente manera:

#Borrado de reglas de la tabla filter
iptables -F INPUT
iptables -F FORWARD
iptables -F OUTPUT
#Borrado de cadenas
iptables -X
#Reseteo del contador de paquetes y bytes
iptables -Z
#Borrado de reglas de la tabla NAT
iptables -t nat -F

Una vez que sabemos esto, vamos a indicar lo que queremos conseguir con nuestro firewall y a definir las reglas que nos permitan conseguirlo.

Permitir conexiones entrantes al firewall vía ssh (para administrar más cómodamente el firewall):

iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A OUTPUT -p tcp -m state --state ESTABLISHED -j ACCEPT

Con estas dos reglas definimos que se permite el tráfico entrante ssh desde cualquier sitio y el tráfico saliente ssh solo para conexiones establecidas.

Permitir navegar a los alumnos de primero de ASIR:

iptables -A FORWARD -p tcp -s 192.168.101.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j ACCEPT
iptables -A FORWARD -p udp -s 192.168.101.0/24 --sport 1024:65535 --dport 53 -m state --state NEW -j ACCEPT
iptables -A FORWARD -d 192.168.101.0/24 -m state --state ESTABLISHED,RELATED -j ACCEPT

Denegar acceso a sitios web Facebook e Instagram a los alumnos de primero de ASIR:

iptables -I FORWARD 1 -p tcp -s 192.168.101.0/24 --dport 443 -m string --string 'facebook' --algo bm -j DROP
iptables -I FORWARD 1 -p tcp -s 192.168.101.0/24 --dport 443 -m string --string 'instagram' --algo bm -j DROP

Permitir navegar a los alumnos de segundo de ASIR:

iptables -A FORWARD -p tcp -s 192.168.102.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j ACCEPT
iptables -A FORWARD -p udp -s 192.168.102.0/24 --sport 1024:65535 --dport 53 -m state --state NEW -j ACCEPT
iptables -A FORWARD -d 192.168.102.0/24 -m state --state ESTABLISHED,RELATED -j ACCEPT

Permitir ping desde la subred de segundo (asi2) a la subred de primero (asi1):

iptables -A FORWARD -s 192.168.102.0/24 -d 192.168.101.0/24 -p icmp -j ACCEPT
iptables -A FORWARD -d 192.168.102.0/24 -s 192.168.101.0/24 -m state --state RELATED,ESTABLISHED -p icmp -j ACCEPT

Permitir navegar a la subred del departamento:

iptables -A FORWARD -p tcp -s 192.168.103.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j ACCEPT
iptables -A FORWARD -p udp -s 192.168.103.0/24 --sport 1024:65535 --dport 53 -m state --state NEW -j ACCEPT
iptables -A FORWARD -d 192.168.103.0/24 -m state --state ESTABLISHED,RELATED -j ACCEPT

Permitir ping desde la subred del departamento (dpto) a la subred de primero (asi1):

iptables -A FORWARD -s 192.168.103.0/24 -d 192.168.101.0/24 -p icmp -j ACCEPT
iptables -A FORWARD -d 192.168.103.0/24 -s 192.168.101.0/24 -m state --state RELATED,ESTABLISHED -p icmp -j ACCEPT

Permitir ping desde la subred del departamento (dpto) a la subred de segundo (asi2):

iptables -A FORWARD -s 192.168.103.0/24 -d 192.168.102.0/24 -p icmp -j ACCEPT
iptables -A FORWARD -d 192.168.103.0/24 -s 192.168.102.0/24 -m state --state RELATED,ESTABLISHED -p icmp -j ACCEPT

Permitir acceso vía ssh desde la subred del departamento a la subred de primero:

iptables -A FORWARD -d 192.168.103.0/24 -s 192.168.101.0/24 -m state --state ESTABLISHED -j ACCEPT
iptables -A FORWARD -s 192.168.103.0/24 -d 192.168.101.0/24 -p tcp --dport 22 -j ACCEPT

Permitir acceso vía ssh desde la subred del departamento a la subred de segundo:

iptables -A FORWARD -d 192.168.103.0/24 -s 192.168.102.0/24 -m state --state ESTABLISHED -j ACCEPT
iptables -A FORWARD -s 192.168.103.0/24 -d 192.168.102.0/24 -p tcp --dport 22 -j ACCEPT

Impedir a asi1 y asi2 navegar al comenzar un examen:

Al permitir la navegación a ambas subredes, debemos denegar el acceso al servidor web insertando una regla en la primera posición, ya que si no prevalecería la regla que permite la navegación. Lo hacemos de la siguiente manera:

iptables -I FORWARD 1 -p tcp -s 192.168.101.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j DROP
iptables -I FORWARD 1 -p tcp -s 192.168.102.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j DROP

Como podemos ver, indicamos con -I que es una inserción de la regla, y que no la estamos añadiendo en la última posición. Después de indicar la cadena, indicamos la posición en la que queremos que se inserte la regla.

Impedir a asi1 y asi2 el acceso al servidor web de servus al comenzar un examen:

iptables -I FORWARD 1 -s 192.168.101.0/24 -d 192.168.102.100 -p tcp --dport 80 -j DROP
iptables -I FORWARD 1 -s 192.168.102.0/24 -d 192.168.102.100 -p tcp --dport 80 -j DROP

Volver a permitir a asi1 y asi2 navegar al finalizar un examen:

iptables -D FORWARD -p tcp -i enp0s8 -s 192.168.101.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j DROP
iptables -D FORWARD -p tcp -i enp0s9 -s 192.168.102.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j DROP

Lo que hacemos es eliminar las reglas insertadas para impedir la navegación con la opción -D

Volver a permitir el acceso al servidor web de servus al finalizar un examen:

iptables -D FORWARD -s 192.168.101.0/24 -d 192.168.102.100 -p tcp --dport 80 -j DROP
iptables -D FORWARD -s 192.168.102.0/24 -d 192.168.102.100 -p tcp --dport 80 -j DROP

Impedir el acceso al servidor web de servus a un nodo con una dirección MAC determinada:

iptables -I FORWARD 1 -p tcp -m mac --mac-source [MAC pc01-t2] -d 192.168.102.100 --dport 80 -j DROP

AUTOMATIZACIÓN

Scripts para la automatización de la configuración del firewall

A continuación, mostramos el contenido de estos tres scripts:

resetear-fw.sh

#Flush  reglas
iptables -F INPUT
iptables -F FORWARD
iptables -F OUTPUT
#Flush cadenas
iptables -X
#Flush contador de paquetes y bytes
iptables -Z
#Flush de la tabla NAT
iptables -t nat -F
#Salida de las subredes a Internet
iptables -t nat -A POSTROUTING -s 192.168.101.0/24 -d 0/0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.102.0/24 -d 0/0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.103.0/24 -d 0/0 -j MASQUERADE
#Política por defecto ACCEPT (por si antes estaba a DROP)
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
#Preguntar política por defecto deseada
echo -e "¿Política por defecto?"
read p
iptables -P INPUT $p
iptables -P FORWARD $p
iptables -P OUTPUT $p
netfilter-persistent save > /dev/null 2>&1
echo -e "Politica por defecto definida: "$p
if [ -z $1 ]; then
    echo "Es necesario el primer argumento"
    exit
elif [ $1 != "i" ] && [ $1 != "o" ]; then
    echo "El primer argumento debe contener el caracter \"i\" (input) o el caracter \"o\" (output)"
    exit
elif [ $1 = "i" ]; then
    iptables -C INPUT -p tcp --dport 22 -j ACCEPT > /dev/null 2>&1
    drop=$?
    if [ -z $2 ]; then
        echo "Es necesario el segundo argumento"
        exit
    elif [ $2 != "a" ] && [ $2 != "d" ]; then
        echo "El segundo argumento debe contener el caracter \"a\" (accept) o el caracter \"d\" (drop)"
        exit
    elif [ $2 = "d" ]; then
        if [ $drop -eq 0 ]; then
            iptables -D INPUT -p tcp --dport 22 -j ACCEPT
            iptables -D OUTPUT -p tcp -m state --state ESTABLISHED -j ACCEPT
            echo "Ahora no se permite input (desde donde sea) y output (para conexiones establecidas) ssh"
        else
            echo "Ahora no se permite input (desde donde sea) y output (para conexiones establecidas) ssh"
        fi
    elif [ $2 = "a" ]; then
        if [ $drop -eq 0 ]; then
            echo "Ahora se permite input (desde donde sea) y output (para conexiones establecidas) ssh"
        else
            iptables -A INPUT -p tcp --dport 22 -j ACCEPT
            iptables -A OUTPUT -p tcp -m state --state ESTABLISHED -j ACCEPT
            echo "Ahora se permite input (desde donde sea) y output (para conexiones establecidas) ssh"
        fi
    fi
elif [ $1 = "o" ]; then
    iptables -C INPUT -m state --state ESTABLISHED -j ACCEPT > /dev/null 2>&1
    drop=$?
    if [ -z $2 ]; then
        echo "Es necesario el segundo argumento"
        exit
    elif [ $2 != "a" ] && [ $2 != "d" ]; then
        echo "El segundo argumento debe contener el caracter \"a\" (accept) o el caracter \"d\" (drop)"
        exit
    elif [ $2 = "d" ]; then
        if [ $drop -eq 0 ]; then
            iptables -D INPUT -m state --state ESTABLISHED -j ACCEPT
            iptables -D OUTPUT -p tcp --dport 22 -j ACCEPT
            echo "Ahora no se permite output (a donde sea) e input (para conexiones establecidad) ssh"
        else
            echo "Ahora no se permite output (a donde sea) e input (para conexiones establecidad) ssh"
        fi
    elif [ $2 = "a" ]; then
        if [ $drop -eq 0 ]; then
            echo "Ahora se permite output (a donde sea) e input (para conexiones establecidad) ssh"
        else
            iptables -A INPUT -m state --state ESTABLISHED -j ACCEPT
            iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT
            echo "Ahora se permite output (a donde sea) e input (para conexiones establecidad) ssh"
        fi
    fi
fi
netfilter-persistent save > /dev/null 2>&1
echo -e "Configuración de iptables guardada."

Este script que acabamos de mostrar borra la configuración existente, establece una política por defecto y, según qué argumentos reciba, permite o deniega el acceso ssh entrant o saliente. Pregunta al usuario que política por defecto desea (ACCEPT/DROP) y recibe dos parámetros. El primero es la dirección (input/output) en la que permitir o denegar ssh (i/o) y el segundo indica si queremos permitir o denegar (a/d).

Ejemplo de uso:

./resetear-fw.sh i a

asir.sh

#ASI1
#Permitimos navegación
iptables -A FORWARD -p tcp -s 192.168.101.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j ACCEPT
iptables -A FORWARD -p udp -s 192.168.101.0/24 --sport 1024:65535 --dport 53 -m state --state NEW -j ACCEPT
iptables -A FORWARD -d 192.168.101.0/24 -m state --state ESTABLISHED,RELATED -j ACCEPT
#Denegar acceso a Facebook e Instagram
iptables -I FORWARD 1 -p tcp -s 192.168.101.0/24 --dport 443 -m string --string 'facebook' --algo bm -j DROP
iptables -I FORWARD 1 -p tcp -s 192.168.101.0/24 --dport 443 -m string --string 'instagram' --algo bm -j DROP
#Impedir a pc02-asi1 (filtrando por su MAC) el acceso al servidor web de servus
#iptables -I FORWARD 1 -p tcp -m mac --mac-source [MAC] -d 192.168.102.100 --dport 80 -j DROP
#ASI2
#Permitimos navegación
iptables -A FORWARD -p tcp -s 192.168.102.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j ACCEPT
iptables -A FORWARD -p udp -s 192.168.102.0/24 --sport 1024:65535 --dport 53 -m state --state NEW -j ACCEPT
iptables -A FORWARD -d 192.168.102.0/24 -m state --state ESTABLISHED,RELATED -j ACCEPT
#Permitimos icmp desde asi2 a asi1
iptables -A FORWARD -s 192.168.102.0/24 -d 192.168.101.0/24 -p icmp -j ACCEPT
iptables -A FORWARD -d 192.168.102.0/24 -s 192.168.101.0/24 -m state --state RELATED,ESTABLISHED -p icmp -j ACCEPT
#DPTO
#Permitimos navegación
iptables -A FORWARD -p tcp -s 192.168.103.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j ACCEPT
iptables -A FORWARD -p udp -s 192.168.103.0/24 --sport 1024:65535 --dport 53 -m state --state NEW -j ACCEPT
iptables -A FORWARD -d 192.168.103.0/24 -m state --state ESTABLISHED,RELATED -j ACCEPT
#Permitimos icmp de dpto a asi1
iptables -A FORWARD -s 192.168.103.0/24 -d 192.168.101.0/24 -p icmp -j ACCEPT
iptables -A FORWARD -d 192.168.103.0/24 -s 192.168.101.0/24 -m state --state RELATED,ESTABLISHED -p icmp -j ACCEPT
#Permitimos icmp de dpto a asi2
iptables -A FORWARD -s 192.168.103.0/24 -d 192.168.102.0/24 -p icmp -j ACCEPT
iptables -A FORWARD -d 192.168.103.0/24 -s 192.168.102.0/24 -m state --state RELATED,ESTABLISHED -p icmp -j ACCEPT
#Permitimos acceso ssh de dpto a asi1
iptables -A FORWARD -d 192.168.103.0/24 -s 192.168.101.0/24 -m state --state ESTABLISHED -j ACCEPT
iptables -A FORWARD -s 192.168.103.0/24 -d 192.168.101.0/24 -p tcp --dport 22 -j ACCEPT
#Permitimos acceso ssh de dpto a asi2
iptables -A FORWARD -d 192.168.103.0/24 -s 192.168.102.0/24 -m state --state ESTABLISHED -j ACCEPT
iptables -A FORWARD -s 192.168.103.0/24 -d 192.168.102.0/24 -p tcp --dport 22 -j ACCEPT

Este script aplica todas las reglas que hemos ido viendo.

examen.sh

if [ -z $1 ]; then
    echo "Es necesario el primer argumento"
    exit
elif [ $1 != "start" ] && [ $1 != "stop" ]; then
    echo "El primer argumento debe contener la cadena \"start\" (comienza el examen) o la cadena \"stop\" (termina el examen)"
    exit
elif [ $1 = "start" ]; then
    iptables -C FORWARD -s 192.168.101.0/24 -d 192.168.102.100 -p tcp --dport 80 -j DROP > /dev/null 2>&1
    start=$?
    if [ $start -eq 0 ]; then
        echo "Empieza el examen. Ahora no se permite ni navegar ni el acceso a Servus"
    else
        iptables -I FORWARD 1 -s 192.168.101.0/24 -d 192.168.102.100 -p tcp --dport 80 -j DROP
        iptables -I FORWARD 1 -s 192.168.102.0/24 -d 192.168.102.100 -p tcp --dport 80 -j DROP
        iptables -I FORWARD 1 -p tcp -s 192.168.101.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j DROP
        iptables -I FORWARD 1 -p tcp -s 192.168.102.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j DROP
        echo "Empieza el examen. Ahora no se permite ni navegar ni el acceso a Servus"
    fi
elif [ $1 = "stop" ]; then
    iptables -C FORWARD -s 192.168.101.0/24 -d 192.168.102.100 -p tcp --dport 80 -j DROP > /dev/null 2>&1
    stop=$?
    if [ $stop -eq 0 ]; then
        iptables -D FORWARD -s 192.168.101.0/24 -d 192.168.102.100 -p tcp --dport 80 -j DROP
        iptables -D FORWARD -s 192.168.102.0/24 -d 192.168.102.100 -p tcp --dport 80 -j DROP
        iptables -D FORWARD -p tcp -s 192.168.101.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j DROP
        iptables -D FORWARD -p tcp -s 192.168.102.0/24 --sport 1024:65535 -m multiport --dports 80,443 -j DROP
        echo "Finaliza el examen. Ahora se permite navegar y el acceso a Servus"
    else
        echo "Finaliza el examen. Ahora se permite navegar y el acceso a Servus"
    fi
fi

Este script recibe un argumento (start/stop) que indica si el examen comienza o termina. En caso de recibir «start» como argumento, impedirá la navegación y el acceso a servus a asi1 y asi2. En caso de recibir «stop» como argumento, eliminará las reglas que impiden la navegación y el acceso a servus.

Ejemplo de uso:

./examen.sh start

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *