Un peu de sécurité sous IPv6

Introduction:

Si vous avez lu mon article précédent, vous avez eu le temps de voir qu'IPv6 est facile à mettre en oeuvre. Néanmoins, ce n'est pas le cas en ce qui concerne la sécurité. Dès qu'une simple station de travail dispose d'une adresse IPv6 publique, elle est directement "attaquable".

En IPv4, vu la pénurie d'adresses, les utilisateurs "à la maison" sont souvent confrontés à du NAT. Si cette méthode a de gros inconvénients (pas vraiment une adresse publique), elle apporte un niveau de sécurité minimal. Chez la majorité des FAI, le routeur ne laisse rien rentrer. C'est d'ailleurs à l'utilisateur de configurer sa machine pour permettre l'accès à des machines internes.

Il n'y a plus rien de tout ça avec IPv6 (mais dans un sens, on ne va pas regretter le NAT) et il faut donc passer un peu de temps pour faire en sorte que "les méchants" ne puissent vous nuire.

Pour les stations dites clientes:

Par station cliente, j'entends une machine connectée directement à Internet en IPv6 mais qui n'offre aucun service réseau: aucun démon qui écoute sur le réseau n'est installé (c'est un peu un raccourci, que les puristes me pardonnent).

Pour ce genre de machine, on veut surtout consommer (utiliser nos globes occulaires), dans de bonnes conditions de performances et pour un maximum d'applications réseau. Le trafic sortant aura donc tout intérêt à être peu filtré car sur ma station de travail, je peux très bien avoir envie de faire autre chose que du web (port 80). Je vais également considérer que le système d'exploitation des stations clientes de mon réseau local est fiable au niveau binaire (c'est du Debian bien administré, pas du Micro$oft non patché): à priori, je n'ai pas de raison de vouloir surveiller ou filtrer ce qui sort de cette machine. Voilà mon compromis de sécurité.

En revanche, pour tout ce qui entre, je veux le strict minimum. Si vous avez lu mon article sur IPv6, vous vous rendez compte que dans minimum, il y a forcément icmpv6. Alors qu'il est courant de fermer les accès ICMP entrant pour toute machine dans un réseau IPv4, on n'a pas du tout intérêt à le faire avec IPv6: le protocole de découverte des voisins et les procédures d'autoconfiguration utilisent ICMPv6. Il faut donc permettre ICMP au moins sur le lien local et sur le préfixe attribué.

Pour terminer, le seul truc qui peut être utile est un accès SSH. Il y a quand même un service qui écoute sur les stations clientes. Mais ce doit être le seul. Ici aussi, je ne souhaite pas administrer ma machine en dehors de mon réseau local.

Voilà pour la partie "cahier des charges" et voici un script qui y répond. Le contenu est explicité par des commentaires. Retenez que c'est presque comme pour ipv4 sauf qu'on utilise ip6tables au lieu de iptables (oui, c'est un raccourci énorme).

Fichier /etc/firewall/netfilter_ipv6.sh

    #!/bin/sh

    # Script de configuration de Netfilter
    ## Cible: Station de travail sur un réseau local en IPv6

    ## Les précautions d'usage: on efface tout.
    ### Suppression de base
    /sbin/ip6tables -F 

    ### Suppression des chaînes utilisateurs
    /sbin/ip6tables -X

    ## Par défaut (default policies), on ferme toutes les entrées sauf les sorties
    /sbin/ip6tables -P INPUT DROP
    /sbin/ip6tables -P FORWARD DROP
    /sbin/ip6tables -P OUTPUT ACCEPT

    ## On accepte l'adresse loopback en entrée/sortie:
    /sbin/ip6tables -A INPUT -i lo -j ACCEPT
    /sbin/ip6tables -A OUTPUT -o lo -j ACCEPT

    ## On accepte ce qui revient:
    /sbin/ip6tables -A INPUT -i wlan0 -m state --state ESTABLISHED,RELATED -j ACCEPT

    ## On accepte tout ce qui vient des liens locaux:
    /sbin/ip6tables -A INPUT -s fe80::/10 -j ACCEPT
    /sbin/ip6tables -A OUTPUT -s fe80::/10 -j ACCEPT

    ## On accepte uniquement SSH en entrant sur le réseau local (préfixe attribué, le lien local et déjà géré)
    /sbin/ip6tables -A INPUT -i wlan0 -s 2001:db8:dead:beef::/64 -p tcp --dport 22 -m state --state NEW -j ACCEPT

    ## On accepte ICMPv6 sur le réseau local (préfixe attribué) pour permettre l'autoconfiguration et les tests sur l'adresse publique (mais pas depuis l'extérieur).
    /sbin/ip6tables -A INPUT -i wlan0 -s 2001:db8:dead:beef::/64 -p icmpv6 -j ACCEPT

Pour le mettre en service dans les règles de l'art sous Debian, je vous conseille la méthode préconisée dans l'article DebianFirewall du wiki Debian (modifier /etc/network/interfaces). Vous noterez que pour définir des adresses IPv6, j'ai cette fois utilisé le préfixe réservé pour les adresses exemples (2001:db8::/32) comme le stipule le RFC 3849

Pour un serveur public:

Ici, notre cas est assez simple mais bien différent de la station cliente: nous avons du trafic public, il faut donc ouvrir plus de portes.

Voici un script du même type qu'avant mais qui tient compte des paramètres suivants:

  • la machine héberge un serveur Web natif IPv6.
  • la machine héberge un MTA qui accepte du trafic IPv6.
  • la machine héberge un serveur IMAP dédié au réseau local ipv6.
  • la machine doit répondre à un ping en provenance du réseau public (on va considérer qu'il y a plus de chances que je cherche à pinguer cette machine depuis Internet que d'autres cherchent à exploiter une faille de sécurité d'ICMPv6 ou de faire un DDOS).

Le script suivant tente de répondre à ces exigences:

    #!/bin/sh

    # Script de configuration de Netfilter
    ## Cible: Serveur Web+Courrier électronique public+des services locaux

    ## Les précautions d'usage: on efface tout.
    ### Suppression de base
    /sbin/ip6tables -F 

    ### Suppression des chaînes utilisateurs
    /sbin/ip6tables -X

    ## On ferme toutes les entrées et les sorties
    /sbin/ip6tables -P INPUT DROP
    /sbin/ip6tables -P FORWARD DROP
    /sbin/ip6tables -P OUTPUT DROP

    ## On accepte l'adresse loopback en entrée/sortie:
    /sbin/ip6tables -A INPUT -i lo -j ACCEPT
    /sbin/ip6tables -A OUTPUT -o lo -j ACCEPT

    ## On accepte tout ce qui vient des liens locaux:
    /sbin/ip6tables -A INPUT -s fe80::/10 -j ACCEPT
    /sbin/ip6tables -A OUTPUT -s fe80::/10 -j ACCEPT

    ## On accepte uniquement SSH en entrant sur le réseau local (préfixe attribué, le lien local et déjà géré)
    /sbin/ip6tables -A INPUT -i eth0 -s 2001:db8:dead:beef::/64 -p tcp --dport 22 -m state --state NEW -j ACCEPT

    ## On accepte ICMPv6 sur le réseau public pour permettre l'autoconfiguration et les tests sur l'adresse publique y compris depuis l'extérieur).
    /sbin/ip6tables -A INPUT -i eth0 -p icmpv6 -j ACCEPT

    # Services publics:
    ## On ouvre les ports 80 et 443 en entrée
    /sbin/ip6tables -A INPUT -i eth0 -p tcp --dport http -j ACCEPT
    /sbin/ip6tables -A INPUT -i eth0 -p tcp --dport https -j ACCEPT

    ## Idem pour les ports 25 et 465 (SMTP)
    /sbin/ip6tables -A INPUT -i eth0 -p tcp --dport smtp -j ACCEPT
    /sbin/ip6tables -A INPUT -i eth0 -p tcp --dport smtps -j ACCEPT

    # Services locaux:
    # # On accepte les connexions IMAP depuis le réseau local (selon le préfixe);
    /sbin/ip6tables -A INPUT -i eth0  -s 2001:db8:dead:beef::/64 -p tcp --dport imaps -j ACCEPT

    ## On accepte les connexions DNS depuis le réseau local (selon le préfixe) pour le cache DNS
    /sbin/ip6tables -A INPUT -i eth0  -s 2001:db8:dead:beef::/64 -p tcp --dport domain -j ACCEPT
    /sbin/ip6tables -A INPUT -i eth0  -s 2001:db8:dead:beef::/64 -p udp --dport domain -j ACCEPT

    ## Gestion de la suite des entrées légitimes
    /sbin/ip6tables -A INPUT  -i eth0 --match state --state ESTABLISHED,RELATED -j ACCEPT

    # Vers l'extérieur:
    ## On autorise l'accès à HTTP/HTTPS (au moins pour les mises à jour système de la machine):
    /sbin/ip6tables -A OUTPUT -o eth0 -p tcp --dport http -j ACCEPT
    /sbin/ip6tables -A OUTPUT -o eth0 -p tcp --dport https -j ACCEPT

    ## On autorise également l'accès vers les autres ports 25 (sinon pas de contact des autres serveurs SMTP):
    /sbin/ip6tables -A OUTPUT -o eth0 -p tcp --dport smtp -j ACCEPT
    /sbin/ip6tables -A OUTPUT -o eth0 -p tcp --dport smtps -j ACCEPT

    ## Gestion de la réponse aux services légitimes en entrée
    /sbin/ip6tables -A OUTPUT -o eth0 --match state --state ESTABLISHED,RELATED -j ACCEPT

    ## On autorise le ping vers l'extérieur:
    /sbin/ip6tables -A OUTPUT -o eth0 -p icmpv6 -j ACCEPT

    # Gestion des logs
    ## On configure la politique par défaut de gestion des logs (avec une chaîne dédiée LOG_DROP)
    ## on affiche [ipv6 netfilter DROP] dans /var/log/syslog lorsqu'un paquet est rejeté.
    /sbin/ip6tables -N LOG_DROP
    /sbin/ip6tables -A LOG_DROP -j LOG --log-level 1 --log-prefix '[ipv6 netfilter DROP]:'
    /sbin/ip6tables -A LOG_DROP -j DROP

    ## Ensuite, on balance tout ce qui reste à traîner dans le LOG (normalement, c'est tout ce qui ne passe pas)
    /sbin/ip6tables -A FORWARD -j LOG_DROP
    /sbin/ip6tables -A INPUT -j LOG_DROP
    /sbin/ip6tables -A OUTPUT -j LOG_DROP

Voilà, tout est assez simple et classique. C'est très proche de ce qu'on trouve avec IPv4 sauf pour la gestion du préfixe réseau. A noter que j'ai ajouté un truc pour voir les logs de ce qui est rejeté: c'est toujours intéressant de savoir quel trafic tente de passer par IPv6 et ça peut me servir pour voir si IPv6 devient un canal de "bordel" ou non. Vous noterez sans doute ce qui peut ressembler à un oubli: il n'y a pas de sortie possible vers le DNS. C'est tout simplement parce que pour l'instant, j'utilise non pas un vrai service DNS sur cette machine mais uniquement un cache utilisé par les stations du réseau local. Ce cache ne sait pas attaquer du DNS en IPv6 (enfin, il n'est pas encore configuré pour).

Petit apparté sur les services qui écoutent en IPv6:

Si vous avez lu mes différents articles qui traitent de la mise en service de protocoles sur Internet (SMTP/IMAP/etc.), vous aurez remarqué qu'on n'y parle pas d'IPv6. Dans la plupart des cas, les logiciels sont "IPv6 compliants" à moins qu'ils disposent d'options spécifiques qui permettent de ne pas gérer IPv6 délibérément. C'est le cas de Exim4 par exemple qui dispose d'une directive disable_ipv6. En revanche, pour les autres applications, l'activation d'IPv6 revient simplement à écouter sur l'adresse ipv6 publique.

En faisant les tests des scripts ci-dessus j'ai été étonné de ne pas avoir certains services disponibles en IPv6. Mais l'erreur était juste une mauvaise configuration. En cas de problème, il vous suffit de lire à nouveau les fichiers de configuration, de vérifier les directives qui précisent les adresses qui "écoutent" et d'ajouter votre adresse IPv6 à la suite. Si vous n'êtes pas certain de qui écoute sur quoi, utilisez la simple commande netstat (genre netstat --inet --inet6 -l) pour comparer ce qui est disponible en IPv4 et ce qui l'est en IPv6.

Attention également à la manière dont doivent être écrites les adresses IPv6. Par exemple, le fichier de configuration de dovecot n'accepte que les adresses IPv6 entre crochets alors que celui d'Exim4 gère très bien sans !

Conclusion:

Ces deux scripts n'ont aucune prétention (certainement pas celle d'être parfaite). Ils permettent une sécurisation à minima de vos machines connectées à Internet IPv6, que ce soit pour des stations de travail ou pour des machines qui exposent des services publics. Ces règles sont assez simples et on peut donc parfaitement les écrire sans autre logiciel qu'un cerveau. Pour des situations plus complexes, la doc de netfilter est un préalable indispensable et il faudra peut-être recourrir à l'aide d'un éditeur de règles.

En attendant, nous voilà un peu plus sûrs de nous dans notre conversion vers IPv6 et c'est tant mieux. Vive le réseau public avec des adresses publiques pour tout le monde (et à foison) ;-)