Introduction à Netfilter et iptables

GNU/Linux Magazine HS n° 041 | avril 2009 | Eric Leblond - Sebastien Tricaud
Creative Commons
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
Afin de vous présenter la base de Netfilter, ainsi que de vous aider dans la compréhension des différents termes employés tout au long de ce hors-série, une introduction ne fera pas de mal. L'approche que nous prenons est pratique : comprendre ce qui est bloqué et utiliser les commandes permettant de le vérifier avec iptables, netcat et tcpdump.

1. Introduction de l'introduction

Par quel bout prendre Netfilter pour en découvrir ses fonctionnalités ? Tenez, prenons donc le réseau. Puisqu'il s'agit de ce que nous voulons : bloquer ce que l'on appelle des paquets. Pour savoir comment un paquet arrive depuis une autre machine vers vos applications, il se passe plusieurs étapes :

1. Une fois que la carte s'assure de l'intégrité des données, elle envoie ce signal à la couche de liaison qui s'assure grâce à l'adresse physique (MAC) de la carte que le signal lui est bien destiné.

2. Le signal remonte au niveau du réseau, et le couple IP source/destination est utilisé. À cette étape, il nous reste deux choix : soit l'adresse IP correspond à celle d'une interface locale, soit elle doit être routée vers une autre machine.

3. Puis, ce signal est transporté vers un protocole de transport (généralement tcp ou udp).

4. Enfin, les données sont délivrées aux applications à l'écoute sur ce port et cette adresse IP.

Pour donner du sens aux règles que nous allons écrire, nous choisissons le schéma suivant :

Il s'agit d'un exemple assez classique comportant un réseau local (LAN) et une zone démilitarisée (DMZ) dans laquelle se trouvent les serveurs. Un ordinateur isolé (Admin, 192.168.1.18) dans le LAN peut aller directement sur Internet pour le protocole HTTP. La DMZ ne contient qu'un seul serveur SMTP (1.2.3.18). Mais, il y a un accès peu recommandable : la machine Admin doit être accessible depuis la machine Maint située à l'extérieur (2.3.4.5) sur ssh (port 22) pour la maintenance.

1.1 Organisation des règles

1.1.1 Filtrage

Netfilter est l'implémentation au niveau du noyau du pare-feu Linux. Iptables est sa petite sœur le manipulant. Netfilter a été conçu avec comme idée d'écrire des règles de pare-feu de manière aussi aisée que l'écriture d'un schéma tel que vous le voyez ci-dessus. Un peu comme si vous le décriviez oralement :

1. Je veux autoriser à tout le monde d'accéder à mon serveur web.

2. Je veux autoriser mon pare-feu à pinguer sur internet.

3. Je veux que les utilisateurs dans mon LAN puissent utiliser le web.

4. J'autorise une exception pour telle adresse IP pour accéder à mon serveur SSH.

Il est facile de classifier les paquets gérés par le pare-feu en trois catégories :

- paquets à destination du pare-feu (4eme cas) ;

- paquets passant par le pare-feu (1er et 3eme cas) ;

- paquets émis par le pare-feu (2eme cas).

Si nous devions choisir un mot pour décrire les trois cas ci-dessus, nous prendrions :

- entrée (INPUT) ;

- aiguiller (FORWARD) ;

- sortie (OUTPUT).

Quand un paquet arrive sur l'interface, le noyau doit prendre une décision de routage. Le paquet qui arrive n'a que deux cas possibles :

- Il est à destination d'une interface locale (INPUT).

- Il est à destination d'une autre machine (FORWARD).

Quant aux paquets partant depuis le pare-feu, ils sont dans le sens de la sortie (OUTPUT). Ce qui donne en schéma :

1.1.2 Translation d'adresse réseau (NAT) de destination

Ce que nous voulons maintenant faire, c'est de rediriger les paquets allant à destination du port 80 sur l'IP du pare-feu vers le serveur web de la DMZ. Cela s'appelle de la traduction d'adresse de destination tout simplement parce que nous changeons l'adresse IP de destination afin que les paquets arrivent sur la bonne machine. Les paquets arrivent sur la chaîne INPUT (port 80 de l'IP du pare-feu), mais doivent être redirigés sur FORWARD (parce qu'ils sont routés sur le serveur web dans la DMZ). Malheureusement, si le noyau décide de router avant que l'adresse IP destination des paquets ne soit changée, il va y avoir un problème de logique.

Heureusement, Netfilter permet grâce à la cible DNAT (Destination NAT) de changer l'adresse IP de destination. Il est cependant nécessaire de le faire avant que la décision de routage soit prise. À cette étape, nous sommes positionnés dans la chaîne de pré-routage (PREROUTING) :

1.1.3 Translation d'adresse réseau (NAT) source

Dans le cas contraire à celui du DNAT, si l'on veut permettre à un utilisateur du LAN d'aller sur un serveur web sur internet, les paquets auront leur IP source modifiée par le pare-feu. Ainsi, les paquets qui passeront par la chaîne FORWARD seront modifiés pour faire comme s’ils provenaient du pare-feu, ce qui aurait voulu dire qu'ils proviennent de la chaîne OUTPUT. Si l'adresse est modifiée avant ces deux chaînes, nous aurons des problèmes parce que les paquets ne proviennent pas de la pile réseau du pare-feu. Il est donc nécessaire d'agir après ces deux chaînes pour non seulement garder une certaine liberté, mais, surtout, ne pas embrouiller l'algorithme de routage. La modification se fera après la décision de routage, ce qui nous donne le schéma final des chaînes de Netfilter :

Ainsi, le code de Netfilter agit sur la chaîne de POSTROUTING pour changer les en-têtes relatives à la source (en faisant du SNAT) ou pour faire d'autres choses assez amusantes.

1.2 Filtrage à états, pour vous faciliter la vie

1.2.1 Ça va dans tous les sens

Comme nous l'avons vu, le filtrage avec Netfilter se veut aussi simple que nous parlons. Tout d'abord, lorsque l'on dit : « Je veux autoriser l'accès à des gens sur mon serveur web à travers mon pare-feu », qu'est-ce qui se passe en réalité et qu'est-ce que nous voulons vraiment ?

Tout d'abord, quand quelqu'un demande une page auprès du serveur web, nous voulons que la réponse arrive jusqu'à cette personne. Afin d'y arriver, il est nécessaire d'autoriser les paquets que notre serveur web renvoie. Ainsi, la phrase correcte serait : « Je veux autoriser l'accès à des gens sur mon serveur web à travers mon pare-feu et je veux que mon pare-feu laisse passer les réponses ». Ceci veut dire que pour chaque accès autorisé, il faut aussi prendre en compte la réponse. Ceci rend le pare-feu ennuyeux, et, du coup, il n'y a plus d'intérêt à continuer à lire ce hors-série. Nan ! Attendez ! « Pour chaque autorisation d'accès, nous voulons prendre en compte la réponse », c'est la réponse !

La phrase « J'autorise les paquets étant des réponses de quelque chose que j'ai déjà autorisé avant » remplit son rôle à merveille. Chaque réponse correspond à ce critère. Maintenant, il ne reste plus qu'à savoir si Netfilter le fait. Vous en doutez ? Vous connaissez certainement déjà la réponse !

1.2.2 Comment Netfilter fait-il pour savoir qu'un paquet est une réponse à un paquet précédemment autorisé ?

Le noyau maintient une table qui enregistre toutes les sessions. Cette table peut être lue depuis le fichier virtuel /proc/net/ip_conntrack ou mieux, en utilisant l'utilitaire conntrack (exemple, pour suivre les évènements, vous pouvez essayer conntrack -E).

Quand un paquet arrive sur une interface, Netfilter regarde à l'en-tête IP afin de voir si ce paquet fait partie d'une session connue. En fonction du cas, il détermine l'état du paquet parmi les cas suivants :

- NEW : le paquet n'est lié à aucune session.

- ESTABLISHED : la session existe au niveau du protocole de la couche transport comme une session TCP.

- RELATED : la session existe de façon relative à une autre, par exemple une session FTP ou le pong qui vient après le ping.

- INVALID : la session est invalide et ne rentre dans aucun des cas cités au-dessus

De cette manière, la phrase « J'autorise les paquets étant des réponses de quelque chose que j'ai déjà autorisé avant » est traduite en « J'autorise les paquets avec l'étant NEW et/ou ESTABLISHED à passer le pare-feu ».

1.2.3 Mais qu'est-ce vraiment qu'une règle Netfilter ?

Réutilisons une de nos phrases : « Je veux autoriser l'accès à des gens sur mon serveur web à travers mon pare-feu ». Un seule règle Netfilter suffira pour dire cela au noyau. Il ne s'agit que d'une décision sur les paquets. Une décision générique est juste « je veux que tous les paquets qui correspondent à ce critère aient la destinée que je choisi ici ».

Comme nous l'avons vu plus haut, nous avons beaucoup de tables et de chaînes sur lesquelles nous pouvons agir (principalement INPUT, OUTPUT et FORWARD). Ainsi, une règle est spécifique à une table. Comme nous avons besoin d'utiliser plus qu'une seule règle, le noyau maintient pour chaque table la liste des règles que nous avons rajoutées à la table. Alors, quand un paquet arrive sur une chaîne, son sort est décidé en regardant s’il correspond à l'un des critères d'une des règles de la chaîne. Cela se fait de la manière la plus simple qui soit. Le noyau prend les règles séquentiellement dans l'ordre qui lui a été donné. Ainsi, l'ordre est crucial, car dès qu'un paquet correspond à une règle, la première que le noyau trouvera sera déterminante. Si aucune règle ne correspond, il faut décider de ce qui sera fait et on lui applique la politique par défaut sur les paquets dans cette chaîne. Cette politique peut-être :

- ACCEPT : le paquet est accepté.

- DROP : le paquet est ignoré.

1.3 Faire les choses

1.3.1 Injecter les règles

Les règles sont injectées dans le noyau en utilisant la ligne de commande iptables. On commence toujours par déterminer la politique à appliquer sur les paquets sur les chaînes, et à mettre cette politique à DROP par défaut, pour que l'on n’ait que les règles de ce que l'on autorise à écrire (c'est plus simple, et c'est plus sécurisé). On tape donc :

iptables -P FORWARD DROP

iptables -P INPUT DROP

iptables -P OUTPUT DROP

1.3.2 Utiliser le suivi de connexion

iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT

iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

1.3.3 Ajouter les règles

Autorisons un accès à notre serveur SMTP depuis n'importe où :

iptables -A FORWARD -m state --state NEW -d 1.2.3.21 -p tcp --dport smtp -j ACCEPT

Autorisons l'accès à notre client vers le serveur web :

iptables -A FORWARD -m state --state NEW -s 192.168.1.18 -p tcp --dport http -j ACCEPT

Et la dernière règle pour l'accès à l'administration par SSH de la machine :

iptables -A FORWARD -m state --state NEW -s 2.3.4.5 -d 192.168.1.18 -p tcp --dport 22 -j ACCEPT

1.3.4 Gérer les traductions d'adresses

Il est nécessaire de faire la traduction de paquet pour notre admin depuis le réseau externe du pare-feu (noté IP_PUBLIQUE) :

iptables -t nat -A POSTROUTING -m state --state NEW -s 192.168.1.18 -p tcp --dport http -j SNAT --to $IP_PUBLIQUE

Vous remarquez ici le rajout de -t nat ? il s'agit en fait de l'utilisation explicite de la table nat. Il existe plusieurs tables que l'on peut utiliser pour manipuler les différentes fonctionnalités de Netfilter. Ces tables sont expliquées en détail dans la section suivante. Il nous faut faire la même chose pour les connexions entrantes depuis la maintenance :

iptables -A FORWARD -m state --state NEW -s 2.3.4.5 -d $PUBLIC_IP -p tcp --dport 22 -j DNAT --to 192.168.1.18

Attention, en cas d'utilisation du forwarding, veuillez bien vous assurer que le sysctl /proc/sys/net/ipv4/ip_forward contient le chiffre « 1 », car le forwarding est désactivé par défaut. Si vous voulez l'activer de façon permanente au démarrage, il suffit d'éditer /etc/sysctl.conf et de positionner la variable net.ipv4.ip_forward à 1.

2. Manipulation des tables avec iptables

Pour rappel, voici un résumé du vocabulaire lorsque l'on utilise la commande iptables :

2.1 Les tables

Plusieurs tables différentes peuvent être utilisées. Chaque table contient plusieurs chaînes prédéfinies. Elles peuvent aussi être personnalisées. Ces chaînes prédéfinies sont :

1. Filter : INPUT, FORWARD, OUTPUT

2. Nat : PREROUTING, POSTROUTING, OUTPUT

3. Mangle : PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING

4. Raw : PREROUTING, OUTPUT

Filter

Cette table permet de faire des opérations de filtrage, ce qui est le plus courant avec iptables, à tel point que c'est la table utilisée par défaut. Les cibles disponibles sont décrites un peu plus loin.

Nat

Nat signifie Network Address Translation (Traduction d'Adresse Réseau). C'est une technique qui entre autres permet à plusieurs machines de se connecter en même temps via une passerelle (qui fait le NAT). Trois cibles sont disponibles : DNAT, SNAT, MASQUERADE. Attention, cette table n'est atteinte que sur les paquet étant dans l'état NEW.

1. DNAT permet de modifier l'ip/port destination.

2. SNAT permet de modifier l'ip/port source.

3. MASQUERADE fonctionne comme SNAT, sauf qu'il n'y a pas besoin de configurer toutes les IP, vu qu'il le fera automatiquement via un processus de vérification.

Mangle

Permet de faire toutes les opérations d'altérations voulues sur les paquets. Trois cibles sont principalement utilisées : TOS, TTL et MARK.

TOS permet de définir ou de changer le type de service d'un paquet en vue d'une politique de routage ultérieure.

TTL permet de changer le champ de la durée de vie d'un paquet.

MARK permet de marquer un paquet afin de l'utiliser ensuite dans une application utilisateur et de faire de la limitation d'utilisation de la bande passante (traffic shaping).

2.2 Chaînes

Une chaîne est une liste de règles qui correspond à une série de paquets. Chaque règle spécifie ce qu'il faut faire lorsque qu'un paquet lui correspond. Cela s'appelle la cible, qui peut aussi être un saut vers une chaîne personnalisée de la même table.

Mise en place d'une chaîne personnalisée

Il est possible d'avoir, en plus des chaînes proposées par défaut par iptables, d'en avoir des personnalisées. Si, par exemple, nous voulons journaliser tous les paquets qui sont refusés, il est beaucoup plus simple de faire :

root@quinificator:~# iptables -N LOG_WWD

root@quinificator:~# iptables -A LOG_WWD -j LOG --log-prefix "Mon Log perso : "

root@quinificator:~# iptables -A LOG_WWD -j DROP

root@quinificator:~# iptables -L -n

Chain INPUT (policy ACCEPT)

target     prot opt source               destination

Chain FORWARD (policy ACCEPT)

target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)

target     prot opt source               destination

Chain LOG_WWD (0 references)

target     prot opt source               destination

LOG        all -- 0.0.0.0/0            0.0.0.0/0           LOG flags 0 level 4 prefix `Mon Log perso : '

DROP       all -- 0.0.0.0/0            0.0.0.0/0

Ensuite, nous pouvons utiliser cette chaîne pour journaliser et supprimer tout ce qui arrive sur le port 3538 :

root@quinificator:~# iptables -A INPUT -p tcp --dport 3538 -j LOG_WWD

root@quinificator:~# iptables -L -n

Chain INPUT (policy ACCEPT)

target     prot opt source               destination

LOG_WWD    tcp -- 0.0.0.0/0            0.0.0.0/0           tcp dpt:3538

Chain FORWARD (policy ACCEPT)

...

3. Les cibles d'iptables

Une cible est une action entreprise suite à la correspondance d'un paquet sur une règle. Il existe deux types de cibles :

- Terminales : celles qui donne le verdict à appliquer au paquet pour qu'il poursuive ou stoppe sa progression dans le noyau. C'est la minorité, mais ce sont aussi celles que vous connaissez le mieux : ACCEPT, DROP, REJECT ou encore NFQUEUE.

- Non-Terminales : celles qui peuvent servir pour journaliser (LOG), modifier le paquet (TTL), remonter à la chaîne parente (RETURN), etc.

Voici les cibles disponibles :

- ACCEPT

Accepter le paquet.

- DROP

Refuser le paquet sans notifier la source.

- REJECT

Refuser le paquet en notifiant la source. Ici, le RST arrive si l'on ajoute l'option --reject-with tcp-rst à la cible.

- QUEUE/NFQUEUE

Envoyer le paquet en espace utilisateur pour décider à ce niveau s’il doit être accepté ou refusé.

- RETURN

Stoppe le parcours de la chaîne courante et continue de parcourir la chaîne parente.

Exemple : dans ce cas précis, nous ne parcourons pas la chaîne LOG_WWD tout simplement parce que INPUT a pour cible RETURN juste au-dessus.

root@quinificator:~# iptables -F

root@quinificator:~# iptables -A INPUT -p tcp --dport 3538 -j LOG_WWD

root@quinificator:~# iptables -I INPUT -p tcp --dport 3538 -j RETURN

root@quinificator:~# iptables -L -n

Chain INPUT (policy ACCEPT)

target     prot opt source               destination

RETURN     tcp -- 0.0.0.0/0            0.0.0.0/0           tcp dpt:3538

LOG_WWD    tcp -- 0.0.0.0/0            0.0.0.0/0           tcp dpt:3538

Chain FORWARD (policy ACCEPT)

target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)

target     prot opt source               destination

Chain LOG_WWD (1 references)

target     prot opt source               destination

LOG        all -- 0.0.0.0/0            0.0.0.0/0           LOG flags 0 level 4 prefix `Mon Log perso : '

DROP       all -- 0.0.0.0/0            0.0.0.0/0

- LOG

Écrire dans le syslog les informations ainsi que le sort réservé au paquet. Par exemple, nous voulons journaliser tous les paquets qui arrivent sur le port 31337.

root@quinificator:~# iptables -A INPUT -p tcp --dport 31337 -j LOG --log-prefix "Mon Log perso : "

produira la ligne suivant dans /var/log/syslog :

Nov 9 15:30:13 localhost kernel: [17197662.000000] Mon Log perso : IN=lo OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:08:00 SRC=127.0.0.1 DST=127.0.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=29142 DF PROTO=TCP SPT=3302 DPT=31337 WINDOW=32792 RES=0x00 SYN URGP=0

- ULOG ou NFLOG

Permet d'écrire dans une application utilisateur comme mysql au lieu de syslog les informations de log d'un paquet comme ce que l'on peut voir avec la cible LOG. Pour le faire, il utilise un socket netlink sur laquelle il faut s'enregistrer.

- DNAT

Utilisé pour le NAT afin d'altérer l’IP et/ou le port de destination.

- SNAT

Utilisé pour le NAT afin d'altérer l’IP et/ou le port source.

- MARK

Permet de marquer un paquet pour ensuite récupérer cette marque pour faire du traffic shaping ou l'utiliser en espace utilisateur (QUEUE).

- MASQUERADE

Utilisé pour les passerelles pour ne pas avoir à configurer toutes les IP à la main pour faire du SNAT.

- MIRROR

Inverse la source et la destination du paquet. À ne pas utiliser.

- REDIRECT

Permet de rediriger une connexion vers un(e) autre machine/port, exactement comme ce que fait DNAT, sans pouvoir modifier l'adresse IP. Mais, on peut mettre plusieurs ports de destination.

- TOS

Utilisé pour la table mangle afin de modifier le type de service. Sert pour le routage, mais reste assez dangereux, car les routeurs ne l'interprètent pas tous de la même façon.

- TTL

Permet à la table mangle de modifier la TTL.

4. Un peu de pratique

4.1 Exemple 1 : Nous fermons le port telnet en entrée :

root@quinificator:~# iptables -A INPUT -p tcp --dport telnet -j DROP

Nous vérifions les tables, ainsi que les statistiques sur les paquets acceptés, ainsi que ceux qui traversent la règle que l'on vient de définir.

root@quinificator:~# iptables -vL -n

Chain INPUT (policy ACCEPT 7 packets, 5270 bytes)

pkts bytes target     prot opt in     out     source               destination

    0     0 DROP       tcp -- *      *       0.0.0.0/0            0.0.0.0/0           tcp dpt:23

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)

pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 6 packets, 353 bytes)

pkts bytes target     prot opt in     out     source               destination

Petite parenthèse : jusqu'à présent, pour simplifier, le paramètre -v n'était pas utilisé. Mais, il reste pourtant essentiel pour la compréhension de la bonne mise en place de vos règles : tout à gauche, il y a la colonne pkts qui indique le nombre de paquets qui sont passés pas votre règle. Ainsi, il est facile de voir si vous avez mis une règle correctement en place. Ça peut s'avérer utile si vous souhaitez ajouter des règles sur un pare-feu déjà en production. Utilisez donc le mode verbeux, ça vous évitera bien des problèmes.

Maintenant que la règle est en place, afin de la vérifier, nous utilisons netcat d'un côté (l'option -l permet d'écouter) :

root@quinificator:~# nc -l -p 23

et telnet de l'autre (on aurait aussi pu utiliser netcat en mode client) :

toady@quinificator:~$ telnet localhost

Trying 127.0.0.1...

C'est tout bon ?

toady@quinificator:~$

Quant à tcpdump pour vérifier :

root@quinificator:~# tcpdump -i any -lpn -s0 -X 'port 23'

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode

listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes

11:19:37.204089 IP 127.0.0.1.4545 > 127.0.0.1.23: S 2218329157:2218329157(0) win 32792

        0x0000: 4510 003c c52e 4000 4006 777b 7f00 0001 E.. 127.0.0.1.23: S 2218329157:2218329157(0) win 32792

0x0000: 4510 003c c52f 4000 4006 777a 7f00 0001 E.. 127.0.0.1.23: S 2218329157:2218329157(0) win 32792

        0x0000: 4510 003c c530 4000 4006 7779 7f00 0001 E..

Nous vérifions que Netfilter a bien reçu les trois paquets :

root@quinificator:~# iptables -vL -n

Chain INPUT (policy ACCEPT 573 packets, 113K bytes)

pkts bytes target     prot opt in     out     source               destination

    3   180 DROP       tcp -- *      *       0.0.0.0/0            0.0.0.0/0           tcp dpt:23

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)

pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 488 packets, 45851 bytes)

pkts bytes target     prot opt in     out     source               destination

root@quinificator:~#

4.2 Exemple 2 : À l'opposé, si le port telnet est ouvert en entrée, cela donne :

root@quinificator:~# iptables -F

root@quinificator:~# iptables -A INPUT -p tcp --dport telnet -j ACCEPT

toady@quinificator:~$ telnet localhost

Trying 127.0.0.1...

Connected to localhost.localdomain.

Escape character is '^]'.

C'est tout bon ?

root@quinificator:~# tcpdump -i any -lpn -s0 -X 'port 23'

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode

listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes

11:26:34.034778 IP 127.0.0.1.2656 > 127.0.0.1.23: S 2647215082:2647215082(0) win 32792

        0x0000: 4510 003c 139f 4000 4006 290b 7f00 0001 E.. 127.0.0.1.2656: S 2647673144:2647673144(0) ack 2647215083 win 32768

        0x0000: 4500 003c 0000 4000 4006 3cba 7f00 0001 E..<..@.@. 127.0.0.1.23: . ack 1 win 4099

        0x0000: 4510 0034 13a0 4000 4006 2912 7f00 0001 E..4..@.@.).....

        0x0010: 7f00 0001 0a60 0017 9dc9 4beb 9dd0 4939 .....`....K...I9

        0x0020: 8010 1003 dc61 0000 0101 080a 001b 5875 .....a........Xu

        0x0030: 001b 5875                                ..Xu

11:26:34.035809 IP 127.0.0.1.2656 > 127.0.0.1.23: P 1:28(27) ack 1 win 4099

        0x0000: 4510 004f 13a1 4000 4006 28f6 7f00 0001 E..O..@.@.(.....

        0x0010: 7f00 0001 0a60 0017 9dc9 4beb 9dd0 4939 .....`....K...I9

        0x0020: 8018 1003 fe43 0000 0101 080a 001b 5875 .....C........Xu

        0x0030: 001b 5875 fffd 03ff fb18 fffb 1fff fb20 ..Xu............

        0x0040: fffb 21ff fb22 fffb 27ff fd05 fffb 23    ..!..''..'.....#

11:26:34.035821 IP 127.0.0.1.23 > 127.0.0.1.2656: . ack 28 win 4096

        0x0000: 4500 0034 114c 4000 4006 2b76 7f00 0001 E..4.L@.@.+v....

        0x0010: 7f00 0001 0017 0a60 9dd0 4939 9dc9 4c06 .......`..I9..L.

        0x0020: 8010 1000 dc49 0000 0101 080a 001b 5875 .....I........Xu

        0x0030: 001b 5875                                ..Xu

11:26:37.243550 IP 127.0.0.1.2656 > 127.0.0.1.23: P 28:46(18) ack 1 win 4099

        0x0000: 4510 0046 13a2 4000 4006 28fe 7f00 0001 E..F..@.@.(.....

        0x0010: 7f00 0001 0a60 0017 9dc9 4c06 9dd0 4939 .....`....L...I9

        0x0020: 8018 1003 fe3a 0000 0101 080a 001b 5b97 .....:........[.

0x0030: 001b 5875 4327 6573 7420 746f 7574 2062 ..XuC'est.tout.b

0x0040: 6f6e 203f 0d0a on.?..

11:26:37.243569 IP 127.0.0.1.23 > 127.0.0.1.2656: . ack 46 win 4096

        0x0000: 4500 0034 114d 4000 4006 2b75 7f00 0001 E..4.M@.@.+u....

        0x0010: 7f00 0001 0017 0a60 9dd0 4939 9dc9 4c18 .......`..I9..L.

        0x0020: 8010 1000 d5f3 0000 0101 080a 001b 5b97 ..............[.

0x0030: 001b 5b97 ..[.

root@quinificator:~# nc -l -p 23

C'est tout bon ?

On voit que nous avons 7 paquets qui sont passés par cette règle.

root@quinificator:~# iptables -vL -n

Chain INPUT (policy ACCEPT 1423 packets, 285K bytes)

pkts bytes target     prot opt in     out     source               destination

    7   419 ACCEPT     tcp -- *      *       0.0.0.0/0            0.0.0.0/0           tcp dpt:23

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)

pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 1244 packets, 114K bytes)

pkts bytes target     prot opt in     out     source               destination

Pour effacer la règle que l'on vient de faire, il suffit de l'effacer avec le numéro qui l'identifie.

root@quinificator:~# iptables -D INPUT 1

root@quinificator:~# iptables -L -n

Chain INPUT (policy ACCEPT)

target     prot opt source               destination

Chain FORWARD (policy ACCEPT)

target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)

target     prot opt source               destination

On peut utiliser l'option --line-numbers, qui affichera le numéro de chaque règle.

5. États

Dans le cas d'un firewall dit « stateful », tel Netfilter, les états permettent de suivre le sens d'une connexion. On peut, par exemple, accepter une connexion entrante uniquement si elle est la réponse d'une communication sortante. Ou inversement. Ou encore refuser une connexion invalide (Attention, invalide a une définition très précise, voir ci-dessous).

Nous avons déjà vu dans l'introduction les 4 états : INVALID, ESTABLISHED, NEW ou RELATED. Nous allons les mettre en pratique et regarder ce qui se passe. Mais avant, il serait bien de revoir un peu le mode de fonctionnement de TCP. Vous pouvez lister à tout moment l'état du conntrack grâce à la commande des conntrack-tools conntrack -L.

5.1 Bref rappel sur TCP

Si nous regardons une session simple sur TCP qui se termine proprement, nous voyons ceci (surtout la sixième colonne) :

12:35:49.004962 IP 127.0.0.1.1501 > 127.0.0.1.9999: S 2746511017:2746511017(0) win 32792

12:35:49.062930 IP 127.0.0.1.9999 > 127.0.0.1.1501: S 2737174071:2737174071(0) ack 2746511018 win 32768

12:35:49.062996 IP 127.0.0.1.1501 > 127.0.0.1.9999: . ack 1 win 4099

12:35:49.932459 IP 127.0.0.1.1501 > 127.0.0.1.9999: P 1:6(5) ack 1 win 4099

12:35:49.932483 IP 127.0.0.1.9999 > 127.0.0.1.1501: . ack 6 win 4096

12:35:50.429504 IP 127.0.0.1.1501 > 127.0.0.1.9999: F 6:6(0) ack 1 win 4099

12:35:50.429973 IP 127.0.0.1.9999 > 127.0.0.1.1501: F 1:1(0) ack 7 win 4096

12:35:50.429989 IP 127.0.0.1.1501 > 127.0.0.1.9999: . ack 2 win 4099

La trace tcpdump peut se résumer à l'image ci-dessous :

En fonction du flux TCP, le suivi de connexion (conntrack) saura si un paquet est ou non une réponse valide. Bien sûr, cela simplifie la réalité, car tout n'est pas aussi simple que cela en à l'air. Par exemple, TCP utilise différents timeouts, qui sont utilisés par le conntrack pour savoir à quel endroit dans les quatre états possibles le paquet ira. Ci-dessous, la liste des timeouts utilisés par les différents états du conntrack :

#define SECS * HZ

#define MINS * 60 SECS

#define HOURS * 60 MINS

#define DAYS * 24 HOURS

unsigned int ip_ct_tcp_timeout_syn_sent =      2 MINS;

unsigned int ip_ct_tcp_timeout_syn_recv =     60 SECS;

unsigned int ip_ct_tcp_timeout_established =   5 DAYS;

unsigned int ip_ct_tcp_timeout_fin_wait =      2 MINS;

unsigned int ip_ct_tcp_timeout_close_wait =   60 SECS;

unsigned int ip_ct_tcp_timeout_last_ack =     30 SECS;

unsigned int ip_ct_tcp_timeout_time_wait =     2 MINS;

unsigned int ip_ct_tcp_timeout_close =        10 SECS;

Et voici la liste des états internes à TCP que le noyau donne au système de suivi de connexion :

- NONE: initial state

- SYN_SENT: SYN-only packet seen

- SYN_RECV: SYN-ACK packet seen

- ESTABLISHED: ACK packet seen

- FIN_WAIT: FIN packet seen

- CLOSE_WAIT: ACK seen (after FIN)

- LAST_ACK: FIN seen (after FIN)

- TIME_WAIT: last ACK seen

- CLOSE: closed connection

5.2 Un contrôle d'état UDP ?

Dans les sources du noyau, dans le fichier linux/net/netfilter/nf_conntrack_proto_udp.c, on a les deux définitions suivantes :

unsigned int nf_ct_udp_timeout = 30*HZ;

unsigned int nf_ct_udp_timeout_stream = 180*HZ;

La première, nf_ct_udp_timeout, signifie à partir de quel moment on estime une connexion terminée lorsque l'on ne reçoit pas de réponse à un paquet (IPS_SEEN_REPLY_BIT). nf_ct_udp_timeout_stream est le timeout mis en place dans un stream (lorsqu'il s'agit d'une réponse à un paquet envoyé au préalable).

HZ correspond à la fréquence du timer définie dans la variable CONFIG_HZ lors de la compilation du noyau. Il s'agit du nombre d'interruption provoquées par le timer en une seconde.

Ainsi, grâce à ces deux variables, Netfilter peut contrôler le flux UDP :

/* Returns verdict for packet, and may modify conntracktype */

static int udp_packet(struct nf_conn *conntrack,

                      const struct sk_buff *skb,

                      unsigned int dataoff,

                      enum ip_conntrack_info ctinfo,

                      int pf,

                      unsigned int hooknum)

{

        /* If we've seen traffic both ways, this is some kind of UDP

           stream. Extend timeout. */

        if (test_bit(IPS_SEEN_REPLY_BIT, &conntrack->status)) {

                nf_ct_refresh_acct(conntrack, ctinfo, skb,

                                   nf_ct_udp_timeout_stream);

                /* Also, more likely to be important, and not a probe */

                if (!test_and_set_bit(IPS_ASSURED_BIT, &conntrack->status))

                        nf_conntrack_event_cache(IPCT_STATUS, skb);

        } else

                nf_ct_refresh_acct(conntrack, ctinfo, skb, nf_ct_udp_timeout);

        return NF_ACCEPT;

}

5.3 Suivi de connexion (connection tracking)

Le suivi de connexion, c'est tout simplement ce que vous venez de voir avec les états. Comme son nom l'indique, il fait un suivi de connexion (les paquets sont défragmentés). Il peut ainsi être utilisé pour mettre en relation plusieurs connexions. Une relation peut être juste comme ce que nous avons vu plus haut sur les états TCP ou UDP (ESTABLISHED), mais il peut aller jusque dans la lecture des protocoles plus bas, afin de savoir quelles sont les nouvelles connexions à attendre (RELATED). C'est le cas de SIP où les paquets de signalisation dans le VoIP donneront les ports à utiliser pour que le flux média (RTP) passe.

L'analyse du protocole se code dans un type de module qui s'appelle « conntrack helper ». Écrire un conntrack helper consiste juste à expliquer au système de suivi de connexions comment estimer qu'une connexion est liée à une autre, simplement en inspectant les paquets qui la composent. Il existe tout un tas de protocoles déjà pris en charge par des modules existants. Dans le cas échéant, sachez qu'il n'est pas (ou ne devrait pas être) difficile d'écrire un tel module. Parmi les modules actuels, on trouve la gestion des protocoles amanda, ftp, h323, irc, netbios, pptp, dccp, gre, sctp, sane, sip ou encore tftp...

5.4 Exemple de suivi de connexion sur FTP

Il faut que le module ip_conntrack_ftp soit chargé. Un des problèmes classiques rencontré avec ftp est l'omission du canal de données. FTP utilise le port 21 et FTP-data le port 20 pour communiquer. FTP-data fonctionne dans le sens opposé de la connexion FTP d'origine. Le fonctionnement de FTP est très particulier, notamment à cause de ses deux modes : actif ou passif.

5.4.1 FTP actif

Dans ce mode, le client FTP envoie, en utilisant la commande PORT, un numéro de port sur lequel le serveur FTP pourra ouvrir une connexion. Une fois la commande analysée, le serveur se connecte sur ce port depuis le port 20 pour envoyer les données. Sans helper pour le protocole FTP, il est impossible de connaître le numéro de port qui a été passé et il est donc nécessaire d'ajouter au jeu de règles une règle générale qui permet les connexions depuis le port 20 du serveur FTP vers les ports éphémères du client.

Mais, grâce au module ip_conntrack_ftp, il est possible de classifier ces connexions du serveur vers le port éphémère du client avec l'état RELATED. Pour cela, le module étudie le flux sur le port 21 et recherche les commandes PORT pour récupérer le port que le client met à disposition du serveur pour la connexion FTP-data. Lorsqu'il détecte la commande PORT, il crée une connexion provisoire (EXPECT) avec les paramètres récupérés et, lorsque le serveur ouvre la connexion FTP-data, il y a correspondance et cette nouvelle connexion est classifiée RELATED.

L'utilitaire conntrack est capable d'afficher la création de ces connexions provisoires. Ainsi, si on lance une connexion FTP vers le serveur 213.146.233.19 et qu'on récupère en mode actif un fichier, on voit la création d'une entrée :

# conntrack -E expect

300 proto=6 src=213.146.233.19 dst=192.168.1.129 sport=0 dport=53473

On a donc bien une connexion provisoire (timeout de 300 secondes) venant du serveur FTP vers un port dynamique du client. Le port source est zéro pour indiquer que l'on ne connaît pas le port source. C'est une insuffisance du module de suivi, puisque si l'on regarde le tcpdump, le port 20 est bien utilisé par le serveur :

11:48:11.385027 IP 213.146.233.19.20 > 192.168.1.129.53473: S 1933400329:1933400329(0) win 5840

11:48:11.385056 IP 192.168.1.129.53473 > 213.146.233.19.20: S 2297039665:2297039665(0) ack 1933400330 win 5792

11:48:11.471918 IP 213.146.233.19.20 > 192.168.1.129.53473: . ack 1 win 5840

Au niveau d'iptables, le nouveau jeu de règles à rajouter est le suivant :

iptables -A INPUT -p tcp --sport 20 --dport 1024: -m state --state RELATED -j ACCEPT

iptables -A OUTPUT -p tcp --sport 1024: --dport 20 -m state --state RELATED -j ACCEPT

5.4.2 FTP passif

Dans le mode passif, c'est le serveur qui envoie la commande PORT au client et le client se connecte ensuite sur le port fourni. Dans ce cas, le modules ip_conntrack_ftp reconnaît aussi la commande PORT émise depuis le serveur et crée une connexion provisoire depuis le client sans port source spécifié vers le serveur sur le port source récupéré par analyse des flux :

# conntrack -E expect

300 proto=6 src=192.168.1.129 dst=213.146.233.19 sport=0 dport=14963

Cette fois encore, le port source est zéro, mais c'est cette fois nécessaire. Les règles iptables correspondantes sont donc :

iptables -A INPUT -p tcp --sport 1024: --dport 1024: -m state --state RELATED -j ACCEPT

iptables -A OUTPUT -p tcp --sport 1024: --dport 1024: -m state --state RELATED -j ACCEPT

5.4.3 Les bugs

Nul logiciel n'est parfait, mais il s'avère que, dès que l'on sort du cadre standard d'iptables, on se retrouve avec pas mal de bugs. Ils ressemblent tout simplement à :

root@quinificator:~# iptables -A INPUT -p tcp --syn --dport 23 -m connlimit --connlimit-above 2 -j REJECT

iptables: Unknown error 4294967295

Il n'y a rien à faire, à part le rapporter, s’il persiste avec la dernière release du noyau.

6. Conclusion

En conclusion, nous pouvons maintenant vous avouer que nous sommes allés un peu plus loin que la simple introduction. Par exemple, il nous a semblé important de montrer comment tester vos règles, afin que vous puissiez comprendre au mieux le fonctionnement de Netfilter. Malheureusement, nous avons dû faire quelques compromis pour laisser un peu de place aux autres articles. Ils approfondissent un bon nombre de sujets et c'est donc l'occasion d'en profiter pour voir comment bloquer des attaques réseau, faire du filtrage sur IPv6, avoir des logs efficaces et encore bien d'autres choses. Bonne lecture !