Il est possible d'usurper des adresses IP à deux niveaux : en tant qu'opérateur réseau ou en tant qu'utilisateur d'un service Internet (accès, machine virtuelle louée,…).
Dans cette page, nous allons nous attarder sur le deuxième type d'usurpation après avoir dit quelques mots sur le premier type.
Les configurations et scripts présentés ci-dessous sont publiquement disponible dans notre git.
Sur Internet, il est possible d'annoncer des ensembles d'adresses IP qui ne vous ont pas été attribuées. Objectifs ?
À l'heure actuelle, on ne peut pas se protéger contre cela (RPKI+ROA apporte la première pierre à une solution possible). En revanche, un opérateur peut être informé d'une annonce, par quelqu'un d'autre, des adresses qui lui ont été attribuées en utilisant un système d'alarme comme BGPmon (service d'OpenDNS aka Cisco depuis 2015). Ces systèmes ont des collecteurs BGP partout dans le monde, les données sont consolidées et une alerte mail est déclenchée quand quelque chose d'anormal se produit (un autre opérateur annonce vos adresses, vos adresses disparaissent de la table de routage globale,…).
Une autre piste est de surveiller la latence entre divers points d'Internet et votre réseau avec le système Atlas du RIPE, par exemple. Une latence qui augmente subitement est le signe d'un re-routage du trafic. Est-il légitime (panne) ou illégitime (attaque) ? C'est à l'humain de le dire.
Sur Internet, comme dans le courrier postal, l'acheminement s'effectue sur la base de l'adresse de destination : l'adresse de l'expéditeur d'un paquet IP n'est pas contrôlée au départ d'un paquet sur le réseau.
Donc, tout possesseur d'un accès à Internet (y compris celui de services comme les machines virtuelles) peut émettre des paquets avec une fausse adresse source, c'est-à-dire une adresse qui n'est pas allouée au fournisseur du service ni allouée par ce fournisseur à vous, client.
Le NAPT complique l'usurpation mais ne l'empêche pas. Voir : Peut-on usurper une adresse IP ?
Plusieurs solutions ont été normalisées à l'IETF, toutes regroupées sous l'appellation « BCP 38 » (38e guide des bonnes pratiques). Voir : RFC 2827: Network Ingress Filtering: Defeating Denial of Service Attacks which employ IP Source Address Spoofing et RFC 3704: Ingress Filtering for Multihomed Networks .
En gros, ça consiste à :
Note : dans cette page, les exemples sont donnés en IPv6 mais ils se transposent très bien en IPv4. ;)
Au début, on pourrait envisager d'utiliser Netfilter, le pare-feu implémenté dans Linux avec un jeu de règles bateau :
# ip6tables -t filter -A INPUT -i eth0 -s 2a00:5881:8100::/40 -m comment --comment "Nos IPs depuis lien externe ?!" -j DROP # ip6tables -t filter -A FORWARD -i eth0 -s 2a00:5881:8100::/40 -m comment --comment "Nos IPs depuis lien externe ?!" -j DROP
Pour la persistance, on utilise netfilter-persistent et iptables-persistent qui permet de charger les règles de filtrage au boot depuis un fichier. Attention à bien installer ces deux logiciels car ils sont complémentaires !
Puis, on découvre des limitations : à chaque nouvelle interconnexion externe (transit, peering), il faudra ajouter 2 règles. Même chose si votre organisation reçoit de nouvelles allocations IP. Le nombre de règles de filtrage va augmenter rapidement. Or, en plus d'être illisible (donc difficile à débug), c'est aussi inefficace : Netfilter a des limites de traitement en nombre de paquets par seconde et un nombre élevé de règles conduit à une diminution drastique des performances. Fâcheux pour des routeurs de bordure…
Si vous utilisez des VLANs, toutes vos interconnexions externes arrivent au final sur la même interface (eth0, par exemple). Netfilter peut toutes les prendre en charge avec une seule règle (notez le « + » après le nom de l'interface qui n'est pas une regex (sinon on autoriserait seulement la répétition du 0) mais qui permet de désigner toutes les interfaces dont le nom commence par eth0) :
# ip6tables -t filter -A INPUT -i eth0+ -s 2a00:5881:8100::/40 -m comment --comment "Nos IPs depuis lien externe ?!" -j DROP # ip6tables -t filter -A FORWARD -i eth0+ -s 2a00:5881:8100::/40 -m comment --comment "Nos IPs depuis lien externe ?!" -j DROP
On remarque que cette proposition ne résout pas le problème de nouvelles allocations IP = nouvelles règles. De plus, chez ARN, nous nommons nos interfaces de communication avec l'extérieur en fonction du nom de l'organisation à l'autre bout… donc « + » ne nous aide pas car le nom de nos interfaces n'ont rien en commun.
On va utiliser les ipset qui permettent de stocker des noms d'interface, des ports, des IPs, des réseaux,… Et des combinaisons de tout cela. On peut ensuite les utiliser avec iptables pour éviter la duplication inutile et contre-productive de règles de filtrage.
Il faut donc installer les outils pour manipuler les ipsets :
# apt-get install ipset
Exemple concret :
# ipset -N bcp38v6 hash:net,iface family inet6 # ipset add bcp38v6 2a00:5881:8100::/40,eth0.42 # ipset add bcp38v6 2a00:5881:8100::/40,eth0.12 # ip6tables -t filter -A INPUT -m set --match-set bcp38v6 src,src -m comment --comment "Nos IPs depuis lien externe ?!" -j DROP # ip6tables -t filter -A FORWARD -m set --match-set bcp38v6 src,src -m comment --comment "Nos IPs depuis lien externe ?!" -j DROP
On crée un ensemble de couples réseau_IPv6,interface_réseau nommé « bcp38v6 » et stocké sous la forme d'une table de hachage (d'autres formats sont dispos).
On utilise ensuite cet ensemble avec iptables. Explications :
La syntaxe d'ipset rappelle celle d'ip(6)tables :
Pour l'instant, rien n'est packagé dans Debian pour gérer la persistance des ipsets. En revanche, netfilter-persistent se contente d'exécuter des scripts (avec run-parts donc ordonnés). Ainsi, pour les règles de filtrage, netfilter-persistent exécute simplement les scripts fournis par iptables-persistent. Attention à bien installer ces deux logiciels car ils sont complémentaires !
Il nous suffit d'écrire notre script sur le modèle de ceux fournis par iptables-persistent (à mettre dans /usr/share/netfilter-persistent/plugins.d/05-ipsets). Ne pas oublier de lui donner les droits d'exécution.
Il est tout à fait possible de créer les sets avec les commandes indiquées au début de cette page mais l'utilisation des commandes « ipset save » et « ipset restore » me paraît plus approprié car similaire au modèle bien connu d'iptables-persistent.
Mais pour que cela fonctionne, il faudra modifier le fonctionnement de netfilter-persistent. En effet, l'utilisation d'un ipset dans une règle de filtrage nécessite que le set existe donc il faut d'abord exécuter le script /usr/share/netfilter-persistent/plugins.d/05-ipsets avant d'exécuter /usr/share/netfilter-persistent/plugins.d/15-iptables. En revanche, lors d'un reload ou arrêt du filtrage, il faut d'abord supprimer la règle de filtrage afin de pouvoir supprimer un ipset. Or, netfilter-persistent lance run-parts de la même façon quelle que soit l'action demandée (start, flush, reload). Donc le script ipset est toujours exécuté en premier… et échoue pour les actions stop/reload.
Notre script /usr/sbin/netfilter-persistent modifié est disponible dans notre dépôt git. Ne pas oublier de lui donner les droits d'exécution.
Évidement, pour que celui-ci ne soit pas écrasé lors d'une mise à jour, nous devons avoir recours à un dpkg-divert avant de stocker le script modifié en /usr/sbin/netfilter-persistent :
# sudo dpkg-divert --add --rename --divert /usr/sbin/netfilter-persistent.divert /usr/sbin/netfilter-persistent
On pourrait se contenter de réutiliser le travail précédent mais :
À l'heure actuelle, ARN propose 3 services à ses adhérents :
Nous n'avons pas à nous occuper des VPN car, d'une part, ils sont situés dans une machine virtuelle, ce qui revient au même que d'éviter les usurpations sur le service hébergement de machines virtuelles et d'autre part car OpenVPN fait déjà le travail avec son mécanisme d'iroute (routes internes). Voir : Tuto serveur OpenVPN - Notion de route interne (iroute).
Pour les machines virtuelles : le nom de leur interface réseau est normé : tap<numéro>. Il suffit donc d'utiliser une règle iptables avec un joker sur l'interface : ip6tables -A FORWARD -i tap+ […]
Pour les machines physiques, nous avons décidé de normer le nommage des interfaces réseau et de le faire débuter par « h- » (comme dans « hébergement »). Il suffit donc d'appliquer une règle de filtrage avec un joker sur l'interface : ip6tables -A FORWARD -i h-+ […]
Hum, mais attends ! Si l'on veut filtrer tout sauf l'IP attribuée à chaque adhérent, ce joker ne peut pas fonctionner puisqu'il faut un couple IPs_autorisées,interface ! Et si l'on attribue plusieurs IPs non contigues à un adhérent, il faut plusieurs règles de filtrage ?! En fait, Linux propose une implémentation du contrôle du routage inverse proposé par les RFC (voir la documentation dans la section « Solutions » ci-dessus) : Linux est capable de vérifier tout seul si un paquet destiné à l'adresse indiquée comme source aurait été envoyé sur la même interface (strict RPF) ou de vérifier qu'une route existe, sans qu'elle passe forcément par l'interface par laquelle le paquet est entré (loose RPF). Il n'y a donc pas besoin de maintenir une liste des couples IPs_interface autorisés.
Avant, on activait ça dans /proc/sys/net/ipv4/conf/*/rp_filter. On remarquera que ce mécanisme n'est pas compatible IPv6. Depuis Linux 3.3, ce mécanisme se présente sous forme d'un module pour Netfilter et prend en charge IPv6.
Exemple d'utilisation en reprenant toutes les contraintes et tous les souhaits formulés ci-dessus :
ip6tables -t raw -A PREROUTING -i tap+ -m rpfilter --invert -m comment --comment "BCP38 machines virtuelles" -j DROP ip6tables -t raw -A PREROUTING -i h-+ -m rpfilter --invert -m comment --comment "BCP38 machines physiques" -j DROP
Ici, nous faisons du Strict RPF (il faut ajouter « –loose » pour faire du loose RPF). « –invert » permet de matcher les paquets qui ne passent pas le test. On utilise bien évidement netfilter-persistent pour charger ces règles au boot.
Et voilà, deux règles de filtrage pour éviter l'usurpation d'adresses, en IPv4 comme un IPv6, sur tous les services actuellement proposés par l'association et ce, de manière automatique.
Note : on ne peut pas utiliser le module rpfilter de Netfilter pour effectuer le filtrage en entrée du réseau au lieu des règles montrées au début de cette page. Réaliser le contrôle sur les interfaces externes, ça se termine souvent mal (cas imprévus, blocage de l'infra, etc.). De plus, dans notre cas, le routage est asymétrique (un paquet peut sortir par Cogent et revenir par Interoute), ce qui oblige à utiliser le mode « loose RPF » qui n'est pas assez contraignant (une route doit exister… oui… ok. Autant ne pas faire de RPF).
Note : sous les systèmes BSD, Packet Filter dispose aussi d'une fonctionnalité de strict RPF. Voir Mettre en œuvre BCP38 avec un routeur FreeBSD en utilisant pf.