Desde la series 1.1, el
kernel de Linux posee en mayor o menor medida capacidad para filtrar tramas. Originalmente (1994),
ipfwadm era la herramienta proporcionada con Linux para la implementación de políticas de filtrado de paquetes en este clon de Unix; derivaba del código de filtrado en BSD (
ipfw), y debido a sus limitaciones (por ejemplo, sólo puede manejar los protocolos TCP, UDP o ICMP)
ipfwadm fue reescrito para convertirse en
ipchains a partir del núcleo 2.1.102 (en 1998). Esta nueva herramienta (realmente, todo el subsistema de filtrado de los núcleos 2.2) introdujo bastantes mejoras con respecto a la anterior, pero seguía careciendo de algo fundamental: el
stateful; era difícil ver a un sistema tan potente como Linux sin una herramienta de
firewalling decente, libre, y `de serie' con el sistema, mientras otros clones de Unix, también gratuitos hacía tiempo que la incorporaban, como es el caso de FreeBSD e
IPFilter.
De esta forma, no es de extrañar que a partir del núcleo 2.3.15 (por tanto, en todos los
kernels estables, de la serie 2.4, desde mediados de 1999)
ipchains fuera sustituido por
iptables, que de nuevo introducía importantes mejoras con respecto a su predecesor. Sin duda la más importante era que ya incorporaba el
stateful no presente en
ipchains, pero no era la única; además,
iptables ofrecía - y de hecho, ofrece - un sistema de NAT (
Network Address Translation) mucho más avanzado, incorpora mejoras en el filtrado (llegando incluso a filtrar en base a la dirección física de las tramas) e inspección de paquetes, y presenta un subsistema de
log mucho más depurado que
ipchains. Por tanto,
iptables es en la actualidad el
software de
firewalling en Linux IPv4; aunque todas las versiones de Linux lo incorporan por defecto, se puede descargar una versión actualizada desde
http://netfilter.samba.org/##∞.
Históricamente, todos los sistemas de firewalling nativos de Linux han sido orientados a comando: esto significa, muy por encima, que no leen su configuración de un determinado fichero, por ejemplo durante el arranque de la máquina, sino que ese archivo de arranque ha de ser un script donde, línea a línea, se definan los comandos a ejecutar para implantar la política de seguridad deseada; esta es una importante diferencia con respecto a otros cortafuegos, como IPFilter (del que hablaremos a continuación), orientados a archivo: en estos la política se define en un simple fichero ASCII con una cierta sintaxis, que el software interpreta y carga en el sistema.
La sintaxis de iptables
(o la de ipchains
, bastante similar) puede llegar a resultar muy compleja si se invoca al sistema de filtrado desde línea de órdenes; por fortuna (o no por fortuna), existen diferentes interfaces para el administrador, algunos tan cómodos e intuitivos como el de Firewall-1, capaces de presentar las políticas de una forma gráfica basada en objetos y de transformar después esas políticas en scripts con las órdenes de iptables
o ipchains
equivalentes. Un ejemplo de estos interfaces es fwbuilder
, disponible libremente desde http://www.fwbuilder.org/##∞.
Para conocer mejor todo el subsistema de filtrado en Linux, así como sus herramientas de gestión, consultas imprescindibles son los
HowTo [Rus00], [Rus02] y [Gre00]; la mayor parte de esta sección está basada en estas obras. Otros documentos que pueden resultar especialmente interesantes son [Mou00] y [Zie01].
iptables o
ipchains son herramientas flexibles, potentes e, igual de importante, gratuitas, que funcionan sobre un sistema operativo también gratuito; quizás para una organización de I+D o para una empresa no muy grande sea difícil permitirse soluciones comerciales cuyo precio puede ascender a varios millones de pesetas, especialmente si se van a instalar cortafuegos internos o arquitecturas DMZ de varios niveles. Sin embargo, no hay excusa para no utilizar este
software de filtrado: un pequeño PC corriendo Linux es más que suficiente para, en muchas ocasiones, garantizar - o al menos incrementar - la seguridad de un laboratorio, un aula informática o un conjunto de despachos.
Arquitectura
En Linux el filtrado de paquetes está construido en el
kernel (se habla con más detalle del núcleo de este sistema operativo en la sección 10.6); en la serie 2.2, para poder utilizar
ipchains hemos de compilar el núcleo con las opciones CONFIG/SMALL>_FIREWALL y CONFIG/SMALL>_IP/SMALL>_FIREWALL activadas, mientras que en las 2.4, para
iptables, hemos de activar CONFIG/SMALL>_NETFILTER: es toda la `instalación' (aparte de las herramientas de gestión de espacio de usuario, que vienen de serie con Linux) que nuestro
firewall va a necesitar, de ahí que en este caso no dediquemos una subsección específica a la instalación del cortafuegos.
Cuando ya estamos ejecutando un núcleo con el
firewalling activado utilizaremos las herramientas de espacio de usuario
ipchains e
iptables para insertar y eliminar reglas de filtrado en él; al tratarse de información dinámica, cada vez que el sistema se reinicie las reglas establecidas se perderán, por lo que es recomendable crear un
script que se ejecute al arrancar el sistema y que las vuelva a definir. Para ello nos pueden resultar útiles un par de
shellscripts que acompañan a las herramientas de espacio de usuario: se trata de
ipchains-save e
ipchains-restore (núcleos 2.2) y de
iptables-save e
iptables-restore (núcleos 2.4); en ambos casos, la primera orden vuelca en pantalla las reglas definidas en el núcleo y la segunda carga dichas reglas desde un archivo.
El núcleo de Linux agrupa las diferentes reglas definidas por el administrador en tres listas denominadas
chains:
input,
output y
forward (en mayúsculas para los
kernels 2.4); en función de las características de una trama, Linux aplica las reglas definidas en cada una de estas listas para decidir qué hacer con el paquete. En primer lugar, al recibir una trama utiliza las reglas de la
chain input (su nombre es autoexplicativo) para decidir si la acepta o no; si las reglas definidas en esta lista indican que se ha de aceptar el paquete, se comprueba a dónde ha de enrutarlo, y en el caso de que el destino sea una máquina diferente al cortafuegos se aplican las reglas de la lista
forward para reenviarlo a su destino. Finalmente, la lista
output se utiliza obviamente antes de enviar un paquete por un interfaz de red, para decidir si el tráfico de salida se permite o se deniega.
Como hemos dicho, los elementos de cada lista se denominan reglas y definen - junto a los
targets, de los que hablaremos a continuación - qué hacer con los paquetes que cumplen ciertas características; si un paquete no cumple ninguna de las reglas de la lista que le corresponde, lo mejor si queremos un sistema seguro es rechazarlo o denegarlo, para lo cual podemos definir un tratamiento por defecto. Mediante
ipchains e
iptables podemos crear listas, modificarlas y eliminarlas17.1 y, lo realmente importante, definir las reglas para cada lista; para estudiar las opciones de ambas órdenes se pueden consultar las páginas
ipchains(8),
ipfw(4),
ipchains-restore(8),
ipchains-save(8) e
iptables(8).
Cuando un paquete cumple cumple una determinada regla de una
chain definimos qué hacer con él mediante lo que se denomina `objetivo' o
target (quizás una traducción menos literal pero más clarificadora sería `acción'). Aunque existen más
targets, son tres los que más se suelen utilizar: ACCEPT permite el paso de un paquete, DENY lo bloquea, y REJECT también lo bloquea pero a diferencia del anterior envía al origen una notificación mediante un mensaje ICMP de tipo DEST/SMALL>_UNREACH (siempre que el paquete bloqueado no sea también de tipo ICMP). Realmente, aunque REJECT y DENY nos parezcan igual de seguros - y de hecho en la mayoría de situaciones lo sean - siempre es más recomendable utilizar DENY, ya que mediante mensajes ICMP un posible atacante podría conseguir información sobre nuestro entorno que en ciertos casos puede comprometer nuestra seguridad, tal y como hemos comentado cuando hablábamos de
Firewall-1.
Gestión
Vamos a ver un ejemplo de definición de una política de seguridad básica utilizando tanto
iptables como
ipchains; por cuestiones de simplicidad nos centraremos exclusivamente en el filtrado de paquetes, no en otros aspectos como la redirección de puertos o el NAT.
Lo primero que posiblemente nos interese antes de comenzar a definir reglas de filtrado sea `vaciar' las
chains, es decir, eliminar todas las reglas asociadas a cada lista, de forma que no interfieran con las que vamos a comenzar a definir; para ello podemos utilizar la opción
`-F' tanto de
iptables como de
ipchains (recordemos que en el primer caso, los nombres de las
chains son los mismos, pero en mayúsculas). Además, podemos definir una política por defecto mediante la opción
`-P' de ambas herramientas; esta política será la que se aplicará cuando un paquete no sea contemplado por ninguna de las reglas de una determinada
chain:
luisa:~# /sbin/ipchains -P input DENY
luisa:~# /sbin/ipchains -F input
luisa:~# /sbin/ipchains -P output ACCEPT
luisa:~# /sbin/ipchains -F output
luisa:~# /sbin/ipchains -P forward DENY
luisa:~# /sbin/ipchains -F forward
luisa:~#
Como vemos, lo que vamos a hacer por defecto es denegar todo el tráfico que se dirija al cortafuegos, tanto si va dirigido a él como si se ha de reenviar a otro sistema, y permitir todo el tráfico de salida. Como vemos, estas políticas por defecto se pueden definir antes de `limpiar' cada
chain, ya que la limpieza sólo afecta a las reglas en sí (y esta acción por defecto no se considera una regla).
Una vez aplicadas las primeras acciones, nos interesará sobre todo, ya que la salida la permitimos por completo (y de las redirecciones de tráfico ya hemos dicho que no vamos a entrar en detalle), definir accesos permitidos a nuestro sistema; por ejemplo, es posible que necesitemos un acceso total al puerto 80 para que todo el mundo pueda maravillarse de esas páginas
web que hemos hecho con
vi. Si es ese el caso, podemos permitir dicho acceso mediante una regla similar a la siguiente:
luisa:~# /sbin/ipchains -A input -p tcp -j ACCEPT -d 158.42.22.41 80
luisa:~#
Estamos indicando que se añada (
`-A') en la
chain `input' (tramas de entrada) una regla que permita (
`ACCEPT') el tráfico TCP (
`-p') cuyo destino (
`-d') sea el puerto 80 de la dirección 158.42.22.41 - en principio, la IP de nuestro servidor
web -. Con
iptables, la sintaxis cambia ligeramente:
luisa:~# /sbin/iptables -A INPUT -p TCP -j ACCEPT -d 158.42.22.41 --dport 80
luisa:~#
Una vez definidas estas reglas, mediante la opción
`-L' de
ipchains e
iptables podemos comprobar que efectivamente se están aplicando (utilizamos también
`-n' para que no se haga resolución DNS):
luisa:~# /sbin/ipchains -L -n
Chain input (policy DENY):
target prot opt source destination ports
ACCEPT tcp
0.0.0.0/0 158.42.22.41 * -> 80
Chain forward (policy DENY):
Chain output (policy ACCEPT):
luisa:~#
Ahora pensemos que quizás también queremos acceder a nuestro servidor de forma remota, utilizando SSH, pero en este caso no desde cualquier lugar de Internet sino desde una dirección concreta; la regla a añadir a la
chain `input' en este caso sería la siguiente:
luisa:~# /sbin/ipchains -A input -p tcp -j ACCEPT -s 158.42.2.1 -d \
> 158.42.22.41 22
luisa:~#
Podemos ver que ahora especificamos la dirección origen (
`-s') desde la que vamos a permitir el tráfico (en este ejemplo,
158.42.2.1). Si utilizáramos
iptables la sintaxis sería la siguiente:
luisa:~# /sbin/iptables -A INPUT -p TCP -j ACCEPT -s 158.42.2.1 -d \
> 158.42.22.41 --dport 22
luisa:~#
Tal y como hemos definido hasta ahora nuestra política de seguridad, sólo estamos permitiendo conexiones al puerto 80 desde cualquier máquina y al puerto 22 desde la especificada; el resto del tráfico de entrada está siendo denegado gracias a la política por defecto que hemos establecido para la
chain input (DENY). Así, tráfico como los mensajes ICMP de vuelta, o las llamadas al servicio
ident que realizan ciertos servidores cuando se les solicita una conexión no alcanzarán su destino, lo cual puede repercutir en la funcionalidad de nuestro entorno: simplemente hemos de pensar en una comprobación rutinaria de conectividad vía
ping o en el acceso a un servidor FTP externo que sea denegado si no se consigue la identidad del usuario remoto. Para evitar estos problemas, podemos permitir el tráfico de ciertos paquetes ICMP y el acceso al servicio
auth (puerto 113):
luisa:~# /sbin/ipchains -A input -p icmp --icmp-type \
> destination-unreachable -j ACCEPT
luisa:~# /sbin/ipchains -A input -p icmp --icmp-type source-quench -j ACCEPT
luisa:~# /sbin/ipchains -A input -p icmp --icmp-type time-exceeded -j ACCEPT
luisa:~# /sbin/ipchains -A input -p icmp --icmp-type parameter-problem \
> -j ACCEPT
luisa:~# /sbin/ipchains -A input -p icmp --icmp-type echo-reply -j ACCEPT
luisa:~# /sbin/ipchains -A input -p tcp -j ACCEPT -d 158.42.22.41 113
luisa:~#
Como vemos, hemos ido definiendo las reglas que conforman nuestra política desde línea de comando; ya hemos comentado que toda esta configuración se pierde al detener el sistema, por lo que es necesario crear un
script que las vuelva a generar y planificarlo para que se ejecute en el arranque de la máquina. Para ello no tenemos que escribir línea a línea la configuración vista en este punto (mejor, la configuración adecuada a nuestro entorno), o utilizar
ipchains-restore o
iptables-restore para leer esa configuración de un fichero; si elegimos esta opción, antes de detener al sistema hemos de ejecutar
ipchains-save o
iptables-save para guardar dicha política en el fichero que posteriormente leeremos. De esta forma, en la parada de la máquina hemos de ejecutar una orden similar a:
luisa:~# /sbin/ipchains-save >/etc/rc.d/policy
Saving `input'.
luisa:~#
Mientras que en el arranque, en nuestro
script cargaremos la política guardada al detener la máquina con una orden como:
luisa:~# /sbin/ipchains-restore </etc/rc.d/policy
luisa:~#
El sistema de log
Evidentemente, tanto
ipchains como
iptables están preparados para generar
logs en el sistema: ambos permiten registrar mediante
syslogd los paquetes que cumplan cierta regla - por norma general, todos -. Un registro exhaustivo de las acciones que se toman en el núcleo con respecto al filtrado de paquetes no es conveniente: la gran cantidad de información guardada hace imposible detectar actividades sospechosas, y además no es difícil que se produzcan ataques de negación de servicio, ya sea por disco ocupado o por tiempo consumido en generar y guardar registros. Por tanto, lo habitual es almacenar sólamente los paquetes que no sean rutinarios (por ejemplo, intentos de conexión desde direcciones no autorizadas, ciertos paquetes ICMP no habituales...).
En el caso de
ipchains el núcleo de Linux, a través de
klogd y de
syslogd, registra estos eventos con prioridad
`info', y al provenir del
kernel (no olvidemos que el subsitema de filtrado forma parte del núcleo del operativo), su tipo es obviamente
`kernel'. Para que cuando un paquete haga
match con una regla se genere un registro hemos de utilizar la opción
`-l'; por ejemplo, si deseamos que cada vez que alguien intente hacer un
finger contra la máquina el tráfico, aparte de ser denegado, registre un
log, ejecutaremos una orden como la siguiente:
luisa:~# /sbin/ipchains -A input -p tcp -j DENY -l -s 0.0.0.0/0 \
> -d 158.42.22.41 79
luisa:~#
Así, si estos registros se almacenan en el fichero
/var/adm/fwdata, sus entradas serán de la siguiente forma:
rosita:~# tail -1 /var/adm/fwdata
Apr 3 02:03:13 rosita kernel: Packet log: input DENY eth0 PROTO=6 \
158.42.2.1:1032 158.42.22.41:79 L=34 S=0x00 I=18 F=0x0000 T=254
rosita:~#
El anterior mensaje nos dice principalmente que un paquete con protocolo 6 (corresponde a TCP en nuestro archivo
/etc/protocols) proveniente de la dirección 158.42.2.1 y destinado a nuestro servicio
finger ha sido denegado; el resto de la información no la veremos aquí (se puede consultar la documentación del producto para ver el significado concreto de todos y cada uno de los campos registrados).
En el caso de
iptables el registro de eventos generado por el subsistema de filtrado es algo diferente al de
ipchains, tanto al hablar de su funcionamiento como de su sintaxis. Ahora el
log se realiza a través de un
target independiente (LOG) que registra las tramas que hacen
match con una regla determinada, y la prioridad de registro ya no es
`info' sino que se puede indicar en la propia línea de órdenes (por defecto es
`warning'). Además, se ha definido una nueva extensión denominada
`limit' que permite restringir el número de registros que una regla puede generar por unidad de tiempo, lo cual es evidentemente útil para evitar que alguien ataque con éxito nuestros recursos mediante un
flood del
log en el subsistema de filtrado.
Volvamos de nuevo al ejemplo anterior, en el que registrábamos los intentos de acceso a nuestro puerto 79 (
finger) para tener constancia de cuándo alguien trataba de obtener información de los usuarios de nuestro sistema; en el caso de
iptables deberíamos definir una regla como la siguiente:
luisa:~# /sbin/iptables -A INPUT -p TCP -m limit -j LOG --log-prefix \
> "FINGER ATTEMPT:" -d 158.42.22.41 --dport 79
luisa:~#
Lo que indicamos mediante esta orden es que genere un mensaje cada vez que alguien envíe tráfico al puerto 79 de la dirección 158.42.22.41 (esto es, cada vez que alguien haga
finger contra la máquina). Podemos observar que ahora definimos el
target LOG (opción
`-j'), y que además aplicamos la extensión
`limit' (parámetro
`-m') para limitar el registro y evitar así ciertas negaciones de servicio; cada vez que esta regla genere un
log añadirá al principio del mismo la cadena
`FINGER ATTEMPT:', lo que nos permite identificar de una forma más clara el mensaje. Podemos fijarnos en que a través de esta regla no estamos ni aceptando ni negando el tráfico, sino sólo registrando su existencia: para detener la trama, hemos de indicarlo explícitamente o bien a través de la acción por defecto que hayamos especificado para la
chain INPUT (mediante la opción
`-P').