Table des matières
Anti-usurpation d'adresses IP
Généralités
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.
Au niveau opérateur
Sur Internet, il est possible d'annoncer des ensembles d'adresses IP qui ne vous ont pas été attribuées. Objectifs ?
- Détourner le trafic destiné à ces adresses pour surveillance ou altération ;
- Usage de ces adresses pour envoyer du spam ou autres usages glop (cette catégorie d'attaque se nomme « détournements rapides »).
À 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.
Au niveau individuel
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 ?
Solutions
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 à :
- En entrée d'un réseau (FAI ou FSI) : il faut supprimer silencieusement tous les paquets IP qui arrivent avec une IP source qui fait partie des blocs d'IP que l'on vous a attribué : vous êtes la seule entité à pouvoir émettre avec ces adresses ! Ne pas appliquer ce filtrage laisse la possibilité de conduire une attaque contre votre infra en utilisant vos IPs, ce qui laisse croire à une attaque interne alors que c'est bien une attaque externe. Attaques possibles ? DDoS en utilisant le récursif ouvert d'ARN contre un adhérent…
- En sortie d'un réseau (FAI ou FSI), il faut supprimer silencieusement tous les paquets IP qui sont au départ avec une IP source qui ne fait pas partie des blocs d'IP que l'on vous a attribué.
Application chez ARN
Note : dans cette page, les exemples sont donnés en IPv6 mais ils se transposent très bien en IPv4. ;)
Filtrage en entrée
Solution simple
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…
Solution plus optimisée
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.
Solution évolutive
Exemple
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 :
- Dans la chaîne INPUT (paquets destinés à la machine elle-même), on compare l'IP source des paquets (le premier « src » dans « src,src » et l'interface d'arrivée du paquet (le deuxième « src ») avec les couples existants dans le set nommé « bcp38v6 ».
- Dans la chaîne FORWARD (paquets qui transitent seulement par la machine), on compare l'IP source des paquets en transit (le premier « src » dans « src,src ») et l'interface d'arrivée du paquet (le deuxième « src ») avec les couples existants dans le set nommé « bcp38v6 ». Attention : si l'on avait mis « –match-set bcp38v6 src,dst », on comparerait l'interface réseau sur laquelle le paquet sera transféré (en plus de l'IP source) ! Sur notre routeur de sortie, cela aurait pour conséquence de supprimer notre trafic à destination de l'extérieur ! Si l'on avait mis « –match-set bcp38v6 dst,src », on comparerait l'IP de destination (en plus de l'interface d'arrivée).
La syntaxe d'ipset rappelle celle d'ip(6)tables :
- ipset -N pour créer un set ;
- ipset -L pour lister l'ensemble des sets existants ;
- ipset -F pour vider un set ou l'ensemble des sets existants ;
- ipset -X pour supprimer un set ou l'ensemble des sets existants.
Persistance
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
Filtrage en sortie
On pourrait se contenter de réutiliser le travail précédent mais :
- Si l'on filtre uniquement en sortie du réseau, les utilisateurs peuvent toujours usurper une IP du réseau, c'est-à-dire une IP non utilisée ou celle d'un autre adhérent. En utilisant Netfilter, le seul moyen d'empêcher cela est de filtrer au plus près de l'utilisateur, sur l'interface réseau qui symbolise son service. Cela revient donc à stocker un couple IP,interface pour chaque adhérent. Un ipset est toujours envisageable mais…
- Il y a beaucoup plus simple et efficace
À l'heure actuelle, ARN propose 3 services à ses adhérents :
- VPN OpenVPN
- Hébergement de machines virtuelles
- Hébergement de machines physiques
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.