UFW et IPv6 : la porte ouverte sur votre VPS
Vos règles UFW et votre pare-feu cloud ne couvrent parfois que l'IPv4, laissant des services exposés en IPv6. Voici pourquoi, et comment combler la faille.
Le piège du pare-feu IPv6 en une phrase
Votre pare-feu protège l'IPv4. Votre VPS possède presque certainement aussi une adresse IPv6 publique, et beaucoup de services écoutent dessus par défaut. Si votre pare-feu ne couvre que l'IPv4, ou si vous vous reposez sur un pare-feu cloud qui ne filtre que l'IPv4, chacun de ces services est joignable depuis tout l'internet en IPv6 alors que votre côté IPv4 semble bien verrouillé. Vous testez un port avec curl, vous voyez une connexion refusée, et vous vous sentez en sécurité. Un attaquant se connecte au même port en IPv6 et entre sans problème.
Ce guide montre d'où vient ce trou sur un VPS Ubuntu 24.04 ordinaire, comment voir exactement ce que vous exposez, et comment le combler. UFW n'est pas le coupable ici. Sur une installation Ubuntu moderne, UFW gère déjà l'IPv6. L'exposition vient des couches autour de lui, et de services dont vous ignoriez qu'ils écoutaient.
Pourquoi votre VPS est en IPv6 dès le départ
Presque chaque VPS aujourd'hui est livré avec une adresse IPv6 publique, souvent un /64 entier, en plus de son adresse IPv4. Vérifiez la vôtre :
ip -6 addr show scope global2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
inet6 2001:db8:2a::1/64 scope globalCe 2001:db8:2a::1 est routable depuis n'importe où sur l'internet, exactement comme votre adresse IPv4. Regardez maintenant ce qui écoute :
sudo ss -tlnpState Recv-Q Local Address:Port Process
LISTEN 0 0.0.0.0:22 sshd
LISTEN 0 [::]:22 sshd
LISTEN 0 127.0.0.1:5432 postgres
LISTEN 0 [::]:8080 docker-proxyLisez attentivement la colonne Local Address. 0.0.0.0:22 signifie « écoute sur toutes les adresses IPv4 ». [::]:22 signifie « écoute sur toutes les adresses IPv6 ». 127.0.0.1:5432 est lié à la boucle locale et n'est pas public du tout, donc la ligne Postgres est sûre. Les deux lignes [::] répondent à tout l'internet en IPv6, et celle de docker-proxy est du genre que vous oubliez avoir démarré.
La plupart des démons se lient à :: d'emblée, car sous Linux une socket :: accepte généralement aussi l'IPv4. La posture par défaut d'un serveur tout neuf est donc « répondre sur les deux piles, partout ». Votre pare-feu est la seule chose qui se dresse devant cela, et c'est pourquoi un pare-feu qui ne voit qu'une seule pile est un vrai problème.
D'où vient réellement le trou IPv6
Il y a quatre sources courantes. Sur une machine donnée, vous pouvez en avoir une, ou plusieurs à la fois.
1. Un pare-feu cloud qui ne filtre que l'IPv4. Beaucoup de pare-feu de fournisseurs et de produits de groupes de sécurité ont grandi autour de l'IPv4 et, soit ignorent l'IPv6, soit exigent des règles IPv6 séparées que vous devez ajouter à la main. Si votre seul pare-feu est celui du tableau de bord du fournisseur et qu'il ne couvre pas l'IPv6, vos services [::] sont ouverts quoi qu'il dise du port 22 en IPv4. Lisez la documentation du pare-feu de votre fournisseur et cherchez précisément le mot IPv6.
2. Des iptables écrites à la main, sans ip6tables. La commande iptables ne touche que les tables IPv4. L'IPv6 a une commande complètement séparée, ip6tables, avec ses propres règles séparées. Si vous avez écrit un script de pare-feu plein de lignes iptables -A INPUT ... sans jamais écrire les règles ip6tables correspondantes, votre pare-feu IPv6 est vide, et une chaîne INPUT vide avec une politique par défaut ACCEPT autorise tout :
sudo ip6tables -L INPUT -nChain INPUT (policy ACCEPT)
target prot opt source destinationCette sortie résume tout le piège sur un seul écran. L'IPv4 est filtrée, l'IPv6 accepte le monde entier.
3. Docker qui publie des ports en passant devant votre pare-feu. Quand vous exécutez docker run -p 8080:80, Docker insère ses propres règles devant celles d'UFW, si bien qu'un port publié est joignable même quand ufw status indique que ce port est refusé, et sur Docker moderne la même chose s'applique en IPv6. Pourquoi Docker contourne UFW, et comment filtrer correctement les ports des conteneurs explique le mécanisme et les correctifs. Voyez les bases de Docker Compose sur un VPS pour comprendre comment ces ports publiés sont déclarés.
4. UFW avec l'IPv6 désactivé. UFW gère bien l'IPv6, mais seulement quand on le lui demande. Vérifiez l'interrupteur :
grep IPV6 /etc/default/ufwUbuntu moderne est livré avec IPV6=yes, donc UFW applique chaque règle aux deux piles. Si vous voyez IPV6=no, venu d'une vieille image ou d'un vieux guide, chaque règle UFW que vous avez écrite ne concerne que l'IPv4, et l'IPv6 reste sans gestion.
Voyez exactement ce que vous exposez
Ne devinez pas. Mesurez-le depuis l'extérieur. Listez d'abord vos écouteurs et notez chacun qui est lié à :: :
sudo ss -tlnp | grep '::'Ensuite, depuis une autre machine, connectez-vous à l'adresse IPv6 publique du serveur et essayez un port que vous croyez fermé :
curl -6 -v http://[2001:db8:2a::1]:8080/Si cela renvoie une page ou une bannière, le port est ouvert en IPv6. Un port fermé vous donne Connection refused ou un délai d'attente dépassé. Pour avoir une image complète, scannez l'adresse IPv6 avec nmap depuis l'extérieur du serveur :
nmap -6 2001:db8:2a::1Chaque port que nmap signale comme ouvert en IPv6 est un port que tout l'internet peut atteindre, quel qu'ait été le résultat de votre scan IPv4. Comparer côte à côte les scans IPv4 et IPv6 est la façon la plus rapide de trouver le trou : tout ce qui est ouvert en -6 mais fermé en IPv4 est un service qui manque à votre pare-feu.
Comblez le trou
Faites en sorte qu'UFW couvre les deux piles, et refusez par défaut. Confirmez l'interrupteur, puis définissez une politique d'entrée en refus par défaut et n'autorisez que ce dont vous avez besoin :
sudo sed -i 's/^IPV6=no/IPV6=yes/' /etc/default/ufw
sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status verboseSi UFW était déjà actif quand vous avez basculé IPV6=yes, le changement ne prend effet qu'après avoir exécuté sudo ufw reload.
ufw status liste chaque règle deux fois, une fois simple et une fois avec un suffixe (v6). Quand vous voyez les lignes (v6), UFW filtre l'IPv6 :
22/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)Si vous gérez iptables à la main, reproduisez chaque règle dans ip6tables, ou passez à nftables, dont les tables inet couvrent l'IPv4 et l'IPv6 au même endroit et éliminent toute cette catégorie d'erreur. Une seule table inet filter nftables est le correctif le plus propre quand vous écrivez les règles vous-même.
Liez à la boucle locale les services que vous ne voulez pas rendre publics. Une base de données, un panneau d'administration ou un point de métriques n'a presque jamais besoin d'une adresse publique. Liez-le à 127.0.0.1 et ::1 pour qu'il n'écoute jamais sur une adresse routable dès le départ. Pour Postgres, définissez listen_addresses = 'localhost'. Pour un serveur applicatif, liez-le à 127.0.0.1 et placez un reverse proxy devant. Fermer l'écouteur vaut mieux que le filtrer avec un pare-feu, car alors il n'y a plus rien à atteindre.
Ne comptez pas sur UFW pour garder les ports publiés par Docker. Publiez les ports des conteneurs vers une adresse précise plutôt que vers chaque interface, par exemple -p 127.0.0.1:8080:80, pour que le port ne soit joignable que depuis l'hôte et ce vers quoi vous faites délibérément du proxy. Quand un conteneur doit vraiment être public, placez-le derrière un reverse proxy Traefik et ne publiez que le proxy, pas chaque application.
Ajoutez des règles IPv6 au pare-feu de votre fournisseur, ou acceptez qu'il ne soit pas votre pare-feu pour l'IPv6 et laissez UFW ou nftables sur l'hôte faire ce travail à la place.
Vérifiez que vous êtes bien fermé
Après vos changements, relancez le même test depuis l'extérieur :
curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1Le port qui répondait auparavant devrait maintenant refuser ou dépasser le délai, et nmap devrait le signaler comme filtré ou fermé. Si un port est encore ouvert, reparcourez les quatre sources ci-dessus : un service toujours lié à :: sans règle devant, une règle Docker placée devant UFW, ou un pare-feu de fournisseur qui n'a jamais vu l'IPv6.
Garder les services sensibles entièrement hors de l'internet public est encore plus solide. Placez SSH et les panneaux d'administration derrière un VPN WireGuard, et filtrez leurs ports avec un pare-feu pour qu'ils ne répondent que sur le tunnel, et la question de l'exposition IPv6 cesse de s'appliquer à eux. Pour ralentir les scans de force brute qui frappent tout ce qui reste public, ajoutez Fail2ban devant SSH par-dessus un pare-feu en refus par défaut.
Si les ports eux-mêmes sont nouveaux pour vous, ce que sont les ports et comment les services écoutent est l'introduction à lire en premier.
FAQ
UFW bloque-t-il l'IPv6 par défaut ?
Sur une installation Ubuntu 24.04 moderne, oui. UFW lit IPV6=yes dans /etc/default/ufw et applique chaque règle à la fois à l'IPv4 et à l'IPv6, et ufw status affiche les règles IPv6 avec un suffixe (v6). Le piège apparaît quand IPV6=no (venu d'une vieille image ou d'un vieux tutoriel), quand vous comptez sur un pare-feu de fournisseur qui ne filtre que l'IPv4, ou quand Docker publie un port en passant devant UFW. Vérifiez l'interrupteur avec grep IPV6 /etc/default/ufw.
Comment vérifier ce que mon VPS expose en IPv6 ?
Exécutez sudo ss -tlnp et notez chaque écouteur dont l'adresse locale commence par [::], ce qui signifie qu'il répond sur chaque interface IPv6. Ensuite, depuis une autre machine, testez directement l'adresse IPv6 publique du serveur avec curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/, ou scannez-la avec nmap -6 YOUR:IPV6::ADDR. Tout port ouvert au scan IPv6 mais fermé en IPv4 est votre trou.
Pourquoi puis-je atteindre le port de mon conteneur Docker alors qu'UFW dit qu'il est bloqué ?
Quand vous publiez un port avec -p, Docker insère ses propres règles de pare-feu devant celles d'UFW, si bien que le port publié est joignable même si ufw status le liste comme refusé. Cela se produit en IPv4, et en IPv6 aussi quand le support IPv6 de Docker est activé. Publiez vers une adresse précise comme -p 127.0.0.1:8080:80, ou placez le conteneur derrière un reverse proxy et ne publiez que le proxy.
Ai-je encore besoin d'un pare-feu IPv6 si mon pare-feu IPv4 est solide ?
Oui. L'IPv4 et l'IPv6 sont deux piles réseau séparées, avec des règles de pare-feu séparées. Un jeu parfait de règles IPv4 ne fait rien pour le trafic IPv6. Si votre VPS a une adresse IPv6 publique, et c'est le cas de presque tous, alors tout service qui écoute sur :: reste joignable en IPv6 jusqu'à ce qu'une règle de pare-feu IPv6 ou une liaison à la boucle locale l'en empêche.
Comment faire écouter un service seulement en IPv4, ou seulement sur localhost ?
Définissez l'adresse de liaison du service dans sa propre configuration. Liez à 127.0.0.1 pour la boucle locale IPv4 seulement, ou à 0.0.0.0 pour toutes les adresses IPv4 sans écouteur IPv6. Postgres utilise listen_addresses, SSH utilise ListenAddress, et la plupart des serveurs applicatifs exposent un paramètre host ou bind. Confirmez le résultat avec sudo ss -tlnp et vérifiez que la colonne Local Address n'affiche plus [::].