Protection du serveur avec iptables

De HomeServer.DIY.
Aller à : Navigation, rechercher

Iptables est un utilitaire en ligne de commande permettant de manipuler le pare-feu integré au noyau Linux, Netfilter. Et quoi qu'on en dise, même sous Linux, en particulier si vous hébergez et donc proposez des services sur Internet, il est nécessaire de serrer un peu la vis. Et iptables fait ça très bien.


Sommaire

Préparation

Il existe plusieurs méthodes pour régler le pare-feu. La méthode la plus directe est de saisir directement une règle dans un shell avec les droits root. Problème, iptables n'a pas de mémoire "automatique", comprenez par là que si vous devez redémarrer le réseau et/ou le serveur (par exemple si le noyau doit être mis à jour), vous perdez toutes les règles. Sauf à utiliser iptables-save (et sa petite soeur iptables-restore), mais comme on est ici pour découvrir iptables, on va la jouer simple.

On va donc préparer un script shell qui sera lancé au démarrage, chargeant toutes les règles dont vous avez besoin. On place généralement ce script dans le dossier /etc/init.d/, qui est le répertoire traditionnellement utilisé pour les scripts de démarrage. Comme on se trouve à priori sur un serveur en ligne de commande, avec les droits administrateurs, on crée le fichier avec la commande suivante :

# nano /etc/init.d/firewall

L'éditeur de texte nano s'ouvre donc sur un fichier vierge. On a prévu d'en faire un script shell, donc on doit spécifier dès la première ligne quel shell il faudra utiliser. On commence donc par insérer cette ligne :

#!/bin/bash

Maintenant on va pouvoir ajouter les règles les unes à la suite des autres. Sachez que par défaut, Netfilter accepte toutes les connexions sur tous les ports, et cherche systématiquement à y répondre. On va donc commencer par tout bloquer, et ensuite ajouter au cas par cas les règles nécessaires pour que vos services restent accessibles.


Phase 1 : blocage total

Ils font peur ces deux mots hein ? Et pourtant, c'est la première étape nécessaire pour être sur que tout fonctionne correctement. Pour commencer on lui dit de virer toutes les règles existantes (histoire de ne pas créer de conflit) :

#Nettoyage des règles existantes
iptables -t filter -F
iptables -t filter -X

Remarque : pour chaque règle ou groupe de règles que vous ajoutez, je vous conseille d'y adjoindre un commentaire décrivant ce que font les commandes. Car si vous revenez sur le script six mois plus tard, vous risquez de ne plus vous souvenir de qui fait quoi ! Le plus simple ici est de précéder le groupe d'une ligne commençant par un #. Ce caractère spécial indique au shell de ne pas tenir compte de ce qui suit jusqu'à la ligne suivante.

Bref, reprenons. Cette fois, on attaque, et on bloque tout. A ce stade, il n'est bien entendu pas recommandé d’exécuter le script, sous peine de ne plus avois accès à la machine :

#Blocage total par défaut
iptables -t filter -P INPUT DROP
iptables -t filter -P FORWARD DROP
iptables -t filter -P OUTPUT DROP

On dit quand même à Netfilter de ne pas fusiller les connexions qui seraient déjà établies :

#Maintien des connexions établies
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

Par contre , on ne vas pas empêcher la machine de communiquer avec elle-même, un comportement qu'on appelle loopback en anglais (lo dans le jargon iptables) :

#Déblocage de la boucle locale
iptables -t filter -A INPUT -i lo -j ACCEPT
iptables -t filter -A OUTPUT -o lo -j ACCEPT

Voilà, maintenant que tout est fermé, on va commencer à débloquer ce qu'il faut pour vos services.


Phase 2 : ouverture des vannes

Avant de foncer tête baissée, on va expliquer un peu le fonctionnement d'iptables. Basiquement toutes les règles auront la même forme et utiliseront les mêmes paramètres. Cette forme est la suivante :

iptables -t filter -A INPUT/OUTPUT -p TCP/UDP --dport port_a_ouvrir -j ACCEPT/DROP

Si ça ne vous parait pas encore clair, rappelez-vous : un service écoute sur un port avec un protocole. Par exemple un serveur web écoute sur le port 80 au moyen du protocole TCP. Les paramètres sont donc les suivants :

  • -t ici vaudra toujours filter
  • -A permettra de dire si la règle s'applique à l'entrée (INPUT) ou à la sortie (OUTPUT). Si la communication doit se faire dans les deux sens, il faudra écrire une règle pour chacun d'eux
  • -p servira à spécifier si le trafic doit être de nature TCP (utilisé par les protocoles tels qu'HTTP, FTP) ou UDP (VoIP et serveurs de jeux)
  • --dport indique quel port surveiller. On peut aussi spécifier une plage de port en séparant le début et la fin par un ":"
  • -j dira si on accepte (ACCEPT) ou refuse (DROP) la connexion. Comme on bloque tout par défaut on utilisera plus souvent ACCEPT.

Comme je viens de le dire pour le paramètre -A, on écrira quasi-systématiquement deux règles pour chaque port ouvert. Car autoriser l'un en interdisant l'autre revient par exemple à le laisser écouter mais l'empêcher de répondre, ce qui n'est pas très pertinent. Et là vous me direz, "mais ne peut-on pas lui dire en une ligne de prendre les deux ?" Sauf que le -A accepte d'autres paramètres que INPUT et OUTPUT, mais comme on cherche à bien empêcher tout trafic sauf celui choisi, on se doit d'être un peu plus précis.

Maintenant que l'on sait ouvrir un port, lesquels ajouter ? Eh bien là, c'est un peu compliqué, car le nombre de services et les ports à ouvrir sont variés, même si beaucoup sont répandus et standardisés. Voici un petit tableau avec les plus courants :

Service Port Protocole
SSH 22 TCP
HTTP 80 TCP
FTP 20 et 21 TCP
SMTP 25 TCP
IMAP 143 TCP
DNS 53 TCP et UDP

Par exemple, si vous avez un serveur web d'installé (typiquement apache2), le couple de règles à ajouter sera le suivant :

#Serveur Web
iptables -t filter -A OUTPUT -p tcp --dport 80 -j ACCEPT
iptables -t filter -A INPUT -p tcp --dport 80 -j ACCEPT

Pour connaitre la liste des services lancés sur votre machine qui écoutent sur un port en particulier, vous pouvez vous servir de netstat :

# netstat -tanpu
 Connexions Internet actives (serveurs et établies)
Proto Recv-Q Send-Q Adresse locale          Adresse distante        Etat        PID/Program name
tcp        0      0 xx.xx.xx.xx:4949        0.0.0.0:*               LISTEN      2189/munin-node
tcp        0      0 0.0.0.0:2269            0.0.0.0:*               LISTEN      2741/sshd
tcp        0    248 xx.xx.xx.xx:2269        xx.xxx.xx.xx:63183      ESTABLISHED 23876/4
udp        0      0 xx.xx.xx.xx:1024        0.0.0.0:*                           31302/codwaw_lnxded
udp        0      0 0.0.0.0:3074            0.0.0.0:*                           31302/codwaw_lnxded
udp        0      0 xx.xx.xx.xx:28960       0.0.0.0:*                           2201/cod2_lnxded
udp        0      0 xx.xx.xx.xx:29000       0.0.0.0:*                           2203/cod2_lnxded

A partir de ce résultat (tronqué pour les besoins de l'article), on peut déduire la liste de ports à ouvrir :

  • 4949 en tcp
  • 2269 en tcp
  • 1024 en udp
  • 3074 en udp
  • 28960 en udp
  • 29000 en udp


Cas particulier du ping

Le ping est un moyen simple et rapide de vérifier qu'une machine est en ligne. Il faut donc le laisser passer. Mais le ping se base sur un protocole spécifique, l'ICMP qui n'a pas de port prédéfini. Les règles sont donc un peu différentes :

#Ping
iptables -t filter -A INPUT -p icmp -j ACCEPT
iptables -t filter -A OUTPUT -p icmp -j ACCEPT

La base est maintenant terminée, vous pouvez sauvegarder (Ctrl+X avec nano), votre script est prêt à être utilisé.


Phase 3 : Finalisation du script et démarrage automatique

Bien, vous avez un joli fichier texte dans lequel se trouvent vos règles prêtes à être appliquées au noyau. Pour le rendre exécutable, c'est très simple :

# chmod +x /etc/init.d/firewall

Vous pouvez maintenant charger le script et tester le résultat :

# /etc/init.d/firewall

Si la mise en place est un succès, vous pouvez alors l'ajouter au démarrage automatique. Sous Debian, la commande à utiliser est alors :

# update-rc.d firewall defaults

Remarque : Depuis Squeeze, un script comme celui que nous venons de construire se verra refusé avec une erreur d'entête LSB manquant. l'entête en question est à ajouter après la déclaration du shell avant les premières commandes :

#!/bin/bash

### BEGIN INIT INFO
# Provides:          firewall
# Required-Start:    $remote_fs $syslog $local_fs $network
# Required-Stop:     $remote_fs $syslog $local_fs $network
# Should-Start:      $named
# Should-Stop:       $named
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Applique des règles iptables
# Description:       Ce script contient le paramétrage du pare-feu
### END INIT INFO

iptables -t filter -F
iptables -t filter -X
(...)


Protections supplémentaires

Il est possible d'aller un peu plus loin qu'un simple blocage/ouverture de ports. Netfilter/iptables sait faire beaucoup plus, et dans le cas de la sécurité qui nous intéresse, certaines capacités sont appréciables.

Déni de service

Communément appelé flood ou DoS (pour Denial of Service) consiste à surcharger une machine de requêtes au point qu'elle ne réponde plus aux demandes légitimes. On peut donc limiter le nombre de demandes de connexion au minimum vital :

#Limitation du flood
iptables -A FORWARD -p tcp --syn -m limit --limit 1/second -j ACCEPT

Attention, si vous allez au-delà de la seconde, la machine risque là aussi de ne plus répondre ! Cette protection est aussi applicable à UDP et ICMP :

iptables -A FORWARD -p udp -m limit --limit 1/second -j ACCEPT
iptables -A FORWARD -p icmp --icmp-type echo-request -m limit --limit 1/second -j ACCEPT

Scan de ports

Le scan de ports consiste a tester tout ou partie des ports d'une machine pour déterminer ceux ouverts et donc potentiellement les services "attaquables". Si on veut se protéger basiquement de ces attaques, on peut malgré tout ajouter la commande suivante :

#Limitation du scan de ports
iptables -A FORWARD -p tcp --tcp-flags SYN,ACK,FIN,RST RST -m limit --limit 1/s -j ACCEPT

Bannir une IP

Attention, bannir simplement une IP voire carrément un range d'IP (pour bloquer un réseau de spammers par exemple), n'est pas forcément la méthode la plus subtile, mais elle marche comme cela :

#Bannissement d'adresse ip
iptables -A INPUT -s adresse_ip -j DROP


Conclusion

Le pare-feu fait partie des innombrables outils qui permettent de blinder plus ou moins efficacement son serveur. Sa syntaxe n'est peut-être pas la plus facile d'accès, mais elle peut s'avérer très vite puissante. iptables est par exemple aussi utilisé pour rediriger du trafic en mode NAT, ce qui permet aux nombreuses *box des FAI français de raccorder un nombre grandissant de matériels derrière une seule adresse IP publique. Mais iptables/Netfilter n'est pas LA solution ultime. Tout en respectant les règles du réseau, rien n'empêche un individu malintentionné de tenter d'accéder frauduleusement en SSH. Pour l'en empêcher, c'est un autre outil qu'il faut mettre en place : Fail2ban.