Prérequis

Pour la compréhension de cet article, il est recommandé de savoir éditer des fichiers, savoir taper des commandes en mode console, avoir des notions minimales de réseau et comprendre le fonctionnement des scripts de démarrage de type System V.

Outils

J'utilise personnellement la distribution Debian Sarge avec un noyau linux 2.6.8 et l'outil iptables en version iptables v1.3.7. Cependant, la méthode devrait fonctionner pour tous les noyaux de la branche 2.6.x, ainsi que pour les version 1.x de iptables.

Un peu de théorie sur iptables

Avant toute chose, iptables n'est pas un firewall. En lui même, iptables n'est qu'un outil qui permet de configurer la pile réseau du noyau Linux. C'est cependant un outil extrêmement puissant, et assez difficile à prendre en main. Une fois que vous maîtriserez cet outil (ce à quoi cet article ne suffira pas), vous appécierez alors le potentiel de la pile réseau offerte par le noyau Linux.

Lorsque qu'un paquet est reçu par une machine GNU/Linux, ce dernider passe par un certain nombre de "chaînes de filtrages" :

  • PREROUTING est la première chaîne par laquelle passe les paquets. Cette chaîne se situe juste avant la prise de décision sur le routage du paquet;
  • INPUT est la chaîne juste après la prise de décision du routage du paquet, lorsque le paquet est à destination de la machine elle-même;
  • FORWARD est la chaîne juste après la prise de décision du routage du paquet, lorsque ce dernier n'est pas à destination de la machine;
  • OUTPUT est la chaîne empruntée par les paquets qui proviennent de la machine elle-même avant qu'ils soient routés vers l'extérieur;
  • POSTROUTING est la chaîne empruntée par les paquets provenants de la machine, en sortie de la chaîne OUTPUT et juste avant que les paquets sortent vers l'extérieur.

Un dessin valant souvent mieux que de longs discours, voici un récapitulatif schématique des chemins parcourus par les paquets :

Chaîne IPTables

Ce schéma est inspiré du tutoriel iptables 1.2.2 de Oskar Andreasson.

Nous allons découvrir par la suite chacune des chaînes un peu plus en profondeur.

Manipulations basiques

Pour ajouter une règle à une chaîne, il faut suivre une des structures suivantes :

iptables -I <nom de la chaîne> -t <nom de la table> <règle>
iptables -I <nom de la chaîne> <position> -t <nom de la table> <règle>
iptables -A <nom de la chaîne> -t <nom de la table> <règle>

La première permet d'ajouter la règle en début de chaîne, la seconde de l'insérer à la position précisée en paramètre et enfin, la dernière de la placer en fin de chaîne. La position des règles dans la chaîne est importante car les paquets sont transformés par la première chaîne dans la liste qui leur correspond. Il est ainsi assez courant de placer en fin de chaîne une règle qui rejette tout afin de bloquer tous les paquets qui n'ont pas été acceptés par les chaînes précédentes.

Par exemple, les règles ci-dessous permettent de router les connexions tcp sur le port 80 vers l'hôte 192.168.50.1 et de rejeter tout le reste :

shell# iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
shell# iptables -I PREROUTING -t nat -p tcp --dport 80 -j DNAT --to 192.168.50.1:80
shell# iptables -A PREROUTING -t nat -j DROP
shell#  iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:80 to:192.168.50.1:80 
DROP       0    --  0.0.0.0/0            0.0.0.0/0           

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Bien entendu cet ensemble de règle est un peu trop strict pour la plupart des usages :) Il est possible de supprimer les règles une par une avec la commande :

shell# iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:80 to:192.168.50.1:80 
DROP       0    --  0.0.0.0/0            0.0.0.0/0           

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination 
shell# iptables -D PREROUTING 1 -t nat
shell# iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DROP       0    --  0.0.0.0/0            0.0.0.0/0           

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Cette manière de supprimer les règles devient rapidement fastidieuse lorsque plusieurs dizaines de règles sont présentes dans les tables. Afin de supprimer d'un seul coup toutes les règles présentes dans un tables, vous pouvez utiliser la commande :

iptables -F -t <nom de la table>

Chaîne PREROUTING

Au sein même des chaînes, il y a plusieurs tables contenant les règles à appliquer. Pour la chaîne PREROUTING, les tables les plus courantes sont :

  • mangle est la table utilisée pour effectuer des modifications dans les entêtes des paquets;
  • nat (pour Network Address Translation) permet de manipuler les champs destinations et sources des paquets afin de modifier leur routage.

La chaîne PREROUTING est la première par laquelle passent les paquets à destination de la machine et/ou transitant par la machine. Elle est donc très fréquemment utilisée pour :

  • router des paquets provenant de l'internet vers des machines du réseau local ne possédant pas d'ip internet;
  • marquer les paquets pour ensuite les router en conséquence, ou bien effectuer des opérations de QuOS (Quality Of Service = Qualité de Service). Il est ainsi possible de répartir la bande passante selon les sous-réseaux, ou ce genre de chose.

L'utilisation principale de la chaîne PREROUTING est liée au DNAT, la table mangle est trop spécifique pour être largement utilisée. Le DNAT permet d'exposer sur internet les services d'une machine qui n'y est pas directement connecté. Par exemple, si votre passerelle internet est 192.168.1.1 et que vous souhaitez rendre disponible l'accès au serveur web présent sur la machine 192.168.1.10 à internet, alors vous allez utiliser le DNAT pour redirigier les connexions vers la passerelle sur le port 80 vers le port 80 de la machine 192.168.1.10 :

shell# iptables -A PREROUTING -t nat -p tcp --dport 80 -j DNAT --to-destination 192.168.50.10:80

Dans le même ordre d'idée, si vous avez un proxy transparent présent sur votre passerelle web, vous devriez rediriger le traffice internet vers ce dernier :

shell# iptables -A PREROUTING -t nat -p tcp --dport 80 -j DNAT --to-port 8888

Chaîne INPUT

La chaîne INPUT est la chaîne traitant les paquets à destination de la machine et qui vont aussitôt après être réceptionnés par les différents services présents sur la machine et qui sont en écoute sur le réseau. Il s'agit donc de la dernière barrière avant l'entre du paquet sur votre machine.

Les tables disponibles pour la chaîne INPUT sont :

  • mangle qui a le même rôle que précédemment;
  • filter qui permet de filtrer (autoriser/rejeter/ignorer/...) les paquets qui s'apprêtent à entrer dans le système.

La table filter est extrêmement intéressante, elle permet de bien se protéger des attaques extérieures en bloquant les tentatives d'intrusion provenant du réseau. En effet, à l'aide de cette table, vous pouvez spécifier quels sont les types de paquets qui peuvent passer et ceux qui ne peuvent pas.

Afin d'identifier un paquet, il existe plusieurs options disponibles dans la commande iptables. Les plus intéressantes sont celles-ci :

  • -s permet de spécifier l'origine du paquet sous la forme d'un réseau (192.168.50.0/24) ou d'un hôte (192.168.50.1);
  • -d permet de spécifier la destination d'un paquet sous la forme d'un réseau ou d'un hôte;
  • -p permet de spécifier le type de protocol encapsulé dans le paquet (tcp, udp, icmp, ...)
  • --dport est accessible lorsque le protocole est udp ou tcp, il permet de spécifier le port de destination du paquet;
  • --sport est accessible lorsque le protocole est udp ou tcp, il permet de spécifier le port source du paquet;
  • -i permet de spécifier l'interface réseau par lequel est entré le paquet.

Associé à ces règles d'identifications, il existe plusieurs actions :

  • ACCEPT permet d'accepter le paquet;
  • DROP permet d'ignorer le paquet;
  • REJECT permet de renvoyer un paquet indiquant que l'accès est impossible.

Il est également possible de rajouter des options de reconnaissance ou de traitement plus pointus en chargeant certains modules dédiés.

Si vous souhaitez par exemple authoriser tous les paquets en provenance du sous-réseau 192.168.50.0/24, ainsi que les paquets internet destinés au port 80, ais bloquer tous les paquets à destination du port 138, voilà ce à quoi pourraient ressembler vos règles :

shell# iptbales -A INPUT -p tcp --dport 138 -j REJECT --reject-with tcp-reset
shell# iptables -A INPUT -s 192.168.50.0/24 -j ACCEPT
shell# iptables -A INPUT -p tcp --dport 80 -j ACCEPT

Chaîne FORWARD

Les tables disponibles pour la chaîne FORWARD sont :

  • mangle qui a le même rôle que précédemment;
  • filter qui fonctionne exactement comme pour la chaîne INPUT.

La chaîne FORWARD est empruntée par les paquets qui transitent par votre machine. Ceci est le cas si votre machine sert de passerelle entre plusieurs réseaux, entre votre réseau local et internet par exemple. Il y a de fortes chances que vous n'ayez pas trop à vous soucier de cette chaîne sur vos ordinateurs de bureau. Sur votre serveur toutefois, vous voulez très certainement limiter la transition des paquets aux seuls provenant de votre réseau local ou à destination de ce dernier.

Par exemple, si votre passerelle offre l'internet à un sous-réseau 192.168.0.0/24, vous pouvez limiter le transite des paquets grâce aux règles :

shell# iptables -A FORWARD -s 192.168.0.0/24 -j ACCEPT
shell# iptables -A FORWARD -d 192.168.0.0/24 -j ACCEPT
shell# iptables -A FORWARD -j DROP

Chaîne OUTPUT

La chaîne OUTPUT est la chaîne empruntée par les paquets juste sortis des services disponibles sur la machine, juste avant leur routage. Les tables et les options disponibles sont les mêmes que celles de la chaîne INPUT.

Chaîne POSTROUTING

La chaîne POSTROUTING est la chaîne empruntée juste après la chaîne OUTPUT et après la décision de routage des paquets. Les tables et les options disponibles sont les mêmes que celles de la chaîne PREROUTING.

Création de chaînes personnalisées

Afin de répondre à des besoins spécifiques, il est possible de mettre en place des chaînes personnalisées pour le traitement des paquets. C'est notamment très couramment utilisé pour logguer certains paquets particuliers afin de détecter des attaques.

La création d'une nouvelle chaîne se fait grâce à l'option -N de la commande iptables, suivi du nom de la chaîne (qui doit bien entendu ne pas être un nom déjà utilisé. Une fois la chaîne créée, vous pouvez alors l'utiliser comme une chaîne standard.

Par exemple, les commandes suivantes permettent de créer une chaîne qui logguent certains paquets avant de les supprimer. Il est ainsi possible de garder une trace des éventuelles tentatives d'attaques contre votre réseau :

shell# iptables -N LOG_DROP
shell# iptables -A LOG_DROP -p udp --dport 138 -j LOG --log-prefix '[FIREWALL] Netbios intrusion udp/138: ' --log-ip-options
shell# iptables -A LOG_DROP -p udp --dport 137 -j LOG --log-prefix '[FIREWALL] Netbios intrusion udp/137: ' --log-ip-options
shell# iptables -A LOG_DROP -j REJECT

Pour que cette chaîne soit utilisée, il suffit de la spécifier comme paramètre -j au lieu de DROP dans les règles. Par exemple :

shell# iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 139 -j LOG_DROP

La suppression des chaînes personnalisées se fait par le biais de l'option -X suivie du nom de la chaîne :

shell# iptables -X LOG_DROP

Exemple complet

Voici un exemple de code utilisant iptables pour mettre en place des règles de firewall pour un serveur à moi. Je l'utilise en tant que script init. Il n'est pas très complet, mais donne déjà une idée des possibilités offertes :

#!/bin/sh
# Copyright 2006 GrdScarabe
# Distributed under the terms of the GNU General Public License v2

## ------------------------- VARIABLES ---------------------------- ##
IPT=/sbin/iptables # iptable executable
OUT=eth1           # internet interface
IN=eth0            # network interface
# IP de l'interface internet
EXT_IP="`ifconfig $OUT | grep 'inet addr' | \r
        awk '{print $2}' | sed -e 's/.*://'`"
# IP de l'interface reseau interne
INT_IP="`ifconfig $IN | grep 'inet addr' | \r
        awk '{print $2}' | sed -e 's/.*://'`"

## ---------------- FONCTIONS D'INITIALISATION --------------------- ##

# Function to clear all entered rules
clear_all_rules () {
        # removing the rules of each table
        for table in filter nat mangle; do
                $IPT -t $table -F
                $IPT -t $table -X
        done;
}

# Function to create the logging chain for dropping packets
create_reject_log_chain () {
        # creation de la chaine
        $IPT -N REJECT_LOG

        # regles de log
        $IPT -A REJECT_LOG -i $OUT -p udp --dport 138 -j LOG \r
                --log-prefix "[FIREWALL] Netbios udp/138 : " \r
                --log-ip-options
        $IPT -A REJECT_LOG -i $OUT -p udp --dport 139 -j LOG \r
                --log-prefix "[FIREWALL] Netbios udp/139 : " \r
                --log-ip-options

        # log par defaut
        $IPT -A REJECT_LOG -j LOG \r
                --log-prefix "[FIREWALL] Unknown : " \r
                --log-ip-options

        # drop de tous les paquets qui passent chaines
        $IPT -A REJECT_LOG -j DROP
}

# Function to set the default policies for the main chains
set_default_policies () {
        # Drop ICMP echo-request messages sent to broadcast 
        # or multicast addresses
        echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
        # Drop source routed packets
        echo 0 > /proc/sys/net/ipv4/conf/all/accept_source_route
        # Enable TCP SYN cookie protection from SYN floods
        echo 1 > /proc/sys/net/ipv4/tcp_syncookies
       # Don't accept ICMP redirect messages
        echo 0 > /proc/sys/net/ipv4/conf/all/accept_redirects
        # Don't send ICMP redirect messages
        echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects
        # Enable source address spoofing protection
        echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter
        # Log packets with impossible source addresses
        echo 0 > /proc/sys/net/ipv4/conf/all/log_martians

        # on recupere la policy par defaut
        if [ "x$1" == "x" ]; then
                # par defaut on rejette tout
                def="DROP"
        else
                def="$1"
        fi

        # default policy for incoming packets
        $IPT -P INPUT $def
        # default policy for outgoing packets
        $IPT -P OUTPUT $def
        # default policy for forwarding packetss
        $IPT -P FORWARD $def
}

## ---------------- GESTION DES SOUS-RESEAUX --------------------- ##

# Give access to everything on the loopback interface
allow_loopback_access () {
        # access to everyting in INPUT
        $IPT -A INPUT -i lo -j ACCEPT
        # access to everything in OUTPUT
        $IPT -A OUTPUT -o lo -j ACCEPT
}

# Mise en place du routage des paquets vers l'internet
add_masquerading(){
        # partage d'internet avec le reseau
        echo 1 > /proc/sys/net/ipv4/ip_forward
        $IPT -A POSTROUTING -t nat -o $OUT -j MASQUERADE
        $IPT -A FORWARD -i $OUT -j ACCEPT
        $IPT -A FORWARD -s 192.168.50.0/24 -j ACCEPT
}

# Tous les paquets sortants vers l'internet sont autorises
# de meme que les paquets provenant du serveur vers le 
# reseau local
allow_outgoing_packets(){
        # les paquets vers internet sont autorises
        $IPT -A OUTPUT -t filter -o $OUT -j ACCEPT
        # les paquets du serveur vers le reseau egalement
        $IPT -A OUTPUT -t filter -o $IN -s $EXT_IP -j ACCEPT
        $IPT -A OUTPUT -t filter -o $IN -s $INT_IP -j ACCEPT
}
## -------------- REGLES POUR L'ACCES AUX SERVEURS ---------------- ##

# Autorisation aux connections en cours de continuer
allow_current_connections(){
        $IPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
        $IPT -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
}

# Add rule for enabling access to dhcp server from subnet
allow_dhcp_access (){
        # accepting dhcp from subnet
        $IPT -A INPUT -i $IN -p udp --dport 67 -j ACCEPT
}

# Add rules for enabling dns requests from the machine
allow_dns_access () {
        # accepting DNS request
        $IPT -A INPUT  -p udp --sport 53 -j ACCEPT
}

# Add rules for enabling web access to the machine
allow_web_access () {
        # access from the web granted
        $IPT -A INPUT -p tcp --dport 80 -j ACCEPT
        $IPT -A INPUT -p tcp --dport https -j ACCEPT
}

# Add rules to allow ssh connections from my pc
allow_ssh_access () {
        # for connections to the machine
        $IPT -A INPUT -p tcp --dport 22 -j ACCEPT
}

## ---------------REGLES DU SOUS-RESEAU 192.168.50.0/24 -------------- ##

# Add rules for allowing ping from local network
allow_ping_from_subnet () {
        # Allow ICMP ECHO REQUESTS from anywhere on local network
        $IPT -A INPUT -s 192.168.50.0/24 -p icmp --icmp-type echo-request -j ACC
EPT
}

# Tout ce qui provient du reseau est sur
allow_all_from_subnet () {
        $IPT -A INPUT -s 192.168.50.0/24 -i $IN -j ACCEPT
}

##### ----------------------------------------------------------- #####
##### ----------------------------------------------------------- #####
##### ----------------------------------------------------------- #####


case "$1" in
        start)
                echo -e "
===== Hermes Firewall v0.5 =====
"

                echo -e "Adresse internet : $EXT_IP"
                echo -e "Adresse locale   : $INT_IP
"

                # -- Initialisation des regles -- #     
                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Creation de la chaine de log.........."
                create_reject_log_chain
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Politiques par defaut................."
                set_default_policies "DROP"
                echo -e "\033[01;32m[OK]\033[00m"

                # -- Regles de base -- #
                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Autorisation des connexions connues..."
                allow_current_connections
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Acces a l'interface loopback.........."
                allow_loopback_access
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Routage vers internet (masquerade)...."
                add_masquerading
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Autorisation sortie des paquets......."
                allow_outgoing_packets
                echo -e "\033[01;32m[OK]\033[00m"

                # -- Configuration du sous-reseau 192.168.50.0/24 -- #
                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Autoriser tout de 192.168.50.0/24....."
                allow_all_from_subnet
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Ping de 192.168.50.0/24 autorises....."
                allow_ping_from_subnet
                echo -e "\033[01;32m[OK]\033[00m"

                # -- Acces aux serveurs -- #
                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Acces au serveur DHCP................."
                allow_dhcp_access
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Acces au serveur DNS.................."
                allow_dns_access
                echo -e "\033[01;32m[OK]\033[00m"


                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Acces au serveur Web.................."
                allow_web_access
                echo -e "\033[01;32m[OK]\033[00m"


                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Acces au serveur SSH.................."
                allow_ssh_access
                echo -e "\033[01;32m[OK]\033[00m"

                # -- On rejette tout le reste -- #
                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Rejet en masse du reste..............."
                $IPT -A INPUT   -j REJECT_LOG
                $IPT -A OUTPUT  -j REJECT_LOG
                $IPT -A FORWARD -j REJECT_LOG
                echo -e "\033[01;32m[OK]\033[00m"
                ;;

        stop)
                # -- On remet tout propre -- #
                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Nettoyage des regles presentes........"
                clear_all_rules
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Politiques par defaut................."
                set_default_policies "ACCEPT"
                echo -e "\033[01;32m[OK]\033[00m"
                ;;

        restart)
                eval $0 stop
                eval $0 start
                ;;

        min)
                # -- On met juste en marche le routage -- #
                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Nettoyage des regles presentes........"
                clear_all_rules
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Politiques par defaut................."
                set_default_policies "ACCEPT"
                echo -e "\033[01;32m[OK]\033[00m"

                # -- Regles de base -- #
                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Autorisation des connexions connues..."
                allow_current_connections
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Acces a l'interface loopback.........."
                allow_loopback_access
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Routage vers internet (masquerade)...."
                add_masquerading
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Autorisation sortie des paquets......."
                allow_outgoing_packets
                echo -e "\033[01;32m[OK]\033[00m"
                ;;

        bloc)
                # -- On bloque tout -- #
                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Nettoyage des regles presentes........"
                clear_all_rules
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "Politiques par defaut................."
                set_default_policies "DROP"
                echo -e "\033[01;32m[OK]\033[00m"

                echo -ne "\033[01;32m*\033[00m"
                echo -ne "On stoppe tout traffic................"
                $IPT -A INPUT   -j DROP
                $IPT -A OUTPUT  -j DROP
                $IPT -A FORWARD -j DROP
                echo -e "\033[01;32m[OK]\033[00m"
                ;;

        status)
                echo -ne ".........TABLE MANGLE........
"
                $IPT -nvL -t mangle
                echo -ne "...........TABLE NAT.........
"
                $IPT -nvL -t nat
                echo -ne ".........TABLE FILTER........
"
                $IPT -nvL -t filter
                ;;

        *)
                echo "Uses : "
                echo "        $0 start to set up firewall rules"
                echo "        $0 stop to disable firewall"
                echo "        $0 restart to reinitialize firewall"
                echo "        $0 min for minimal service"
                echo "        $0 bloc for stopping traffic"
                echo "        $0 status for displaying rules"
                ;;
esac