lalahop

Chez ARN, FAI associatif alsacien, on a mis en place un serveur DNS récursif-cache ouvert il y a un peu moins d'un an. Il y a quelques précautions à prendre et je n'ai pas trouvé un « how-to » qui ferait le tour de l'essentiel de la problématique.

Attention : ce billet n'est pas à prendre comme parole d'évangile. Il s'agit juste d'un retour d'expérience et de quelques notes d'un n00b qui s'est penché sur le sujet.

Table des matières

Définitions sous-jacentes

Je n'expliquerai pas le principe des attaques par amplification+réflexion, que ça soit en utilisant le protocole DNS ou non, mais je vous donne quelques ressources intéressantes concernant ce sujet :

Je ne détaillerai pas non plus les directives de configuration pour ouvrir votre récursif-cache.

Pourquoi mettre à disposition un serveur DNS récursif-cache ouvert ?

C'est en effet l'aspect le plus important : si l'on ne le comprend pas, on arrive à la conclusion qu'un récursif-cache qui répond uniquement aux blocs d'IPs qui ont été attribués à l'association suffit amplement. Mon avis sur cette question (liste non ordonnée) :

  • Parce qu'on peut le faire. 🙂
  • Parce que c'est enrichissant techniquement (montée en compétences) de chercher comment proposer un récursif-cache ouvert de manière sûre (en nuisant le moins possible à autrui).
  • Quelles garanties offrent les FAI et les gros (Google Public DNS, OpenDNS) en terme de neutralité des réponses ? Pour les FAI, on a des antécédents pour juger sur pièces : pub si réponse inexistante il y a quelques années, copwatch-idf, ARJEL, TPB en Belgique, ... La censure via le DNS est à la mode. OpenDNS mentait (oui, c'était désactivable, avec un compte mais galère si IP dynamique) et ils peuvent recommencer. Nuançons toutefois : les FAI associatifs ne sont pas des associations de malfaiteurs et sont soumises au même droit que les autres FAI. Ce qui implique de se conformer à une décision judiciaire ... quand on est parmi les parties citées à comparaître ... ce qui n'est pas le cas actuellement (oui, oui, je sais, le principe d'égalité devant la loi). Attention : je ne dis pas que les FAI associatifs sont une échappatoire mais simplement que les adhérents seront forcément consultés/informés de toute mesure prise qui serait contraire à l'éthique de l'association, ce qui est déjà une avancée majeure comparée à rester dans l'ignorance avec un fournisseur de récursif DNS sans éthique.
  • Le serveur DNS récursif-cache utilisé, tout comme le FAI, est un point assez central dès que l'on parle de vie privée : le FAI voit tout puisqu'il est l'intermédiaire obligatoire entre votre réseau et le reste des Internets, le récursif-cache voit toutes les requêtes DNS et comme la quasi-totalité des communications sur Internet commencent par une requête DNS ... Pour plus d'informations, je vous invite à lire DNS privacy considerations. Donc, c'est important que des structures qui s'engagent à être correctes au niveau de la neutralité proposent des solutions (argument de Julien Vaubourg). Utiliser un récursif-cache alternatif à celui de son FAI sans éthique n'empêche pas ce dernier de tout voir et de pouvoir altérer, mais c'est un début.
  • Dès qu'il y a un intermédiaire, qu'il soit associatif ou non, on peut avoir un doute légitime le concernant. En réaction, on se monte tous un récursif-cache validant à la maison sans forward vers un récursif commun ? Quid alors de la charge induite sur les serveurs DNS qui font autorité (on ne bénéficie plus de mutualisation du cache) ? Est-ce à eux de supporter le coût ? On notera au passage la disparité que cela produira entre les gros hébergeurs DNS/les TLD, qui pourront absorber le trafic supplémentaire (si l'on suit le raisonnement à terme), et les autres, ce qui conduira à une concentration des acteurs. Nuançons : oui, les serveurs de noms qui font autorité pour guiguishow.info. sont beaucoup, beaucoup moins sollicités que ceux qui font autorité sur info. et je n'ai aucun doute que la racine et les TLD tiendront le choc. Je pense que chaque réseau (at home, petite grappe de serveurs, ...) devrait avoir son récursif-cache validant, local et de confiance qui forward simplement les requêtes à des récursifs-cache mutualisés de confiance (pour la partie éthique / neutralité des réponses). On obtient un effet de strates (comme avec NTP) qui permet de ne pas surcharger les serveurs qui font autorité tout en gagnant une validation DNSSEC locale donc sûre ainsi qu'en performance.
  • Un récursif-cache neutre et validant, ça ne court par les rues. C'est pour cette raison que je pense qu'il est du devoir des FAI associatifs de proposer ce type de service. FDN, FAI associatif, propose un récursif neutre mais il ne fait pas de validation DNSSEC (on pourrait nuancer l'intérêt de ce point avec le problème du dernier kilomètre : la validation doit se faire au plus près du demandeur, de l'utilisateur final pour offrir toutes ses garanties), DNS-OARC propose un récursif-cache validant mais on n'a aucune garantie sur la neutralité des réponses données.
  • Parce qu'il ne doit pas y avoir que les gros (Google, OpenDNS) qui puissent le faire sans crainte sous prétexte qu'ils ont le blé/l'image/les ressources/les couilles/les ovaires/autre. Liberté d'entreprendre, tout simplement. Je veux pouvoir monter un récursif DNS parce que c'est faisable. Point. Je veux avoir la possibilité de le faire moi-même ou dans le cadre de mon association FAI favorite.
  • Il peut aussi y avoir un besoin d'itinérance. Je ne suis pas toujours connecté depuis ma ligne ADSL/mon VPN ARN. Répondre à ce besoin par une recommandation d'utiliser un VPN, ça me semble possible en entreprise mais décalé en dehors de ce cadre-là. Ça peut aussi constituer une première approche de nos idées, de notre cause par un futur adhérent. Un récursif-cache ouvert sert aussi pour des "démos" (au sens large) : commençons par arrêter de taper dig @8.8.8.8 / echo "nameserver 8.8.8.8" > /etc/resolv.conf quand on fait des tests/du debug devant/chez des non inités à notre cause et on aura alors plus de crédibilité pour les convaincre puisqu'il s'agit là d'un simple effet de prescription "si cet ami dont je pense qu'il a un bon niveau technique utilise Google Public DNS, alors ce service sera très bien pour moi".
  • Dès que l'on parle de mettre en place un serveur DNS récursif-cache ouvert, les yeux des interlocuteurs s'écarquillent. Mais quid des clusters NTP ouverts volontairement et surveillés ? Quid des serveurs de jeux vidéos (récupérer le statut sur Call of Duty (et d'autres) = UDP = sensible à une attaque par réflexion) ? Si l'on doit tout fermer et se la jouer individualisme, on ne va pas s'en sortir, à mon humble avis. On notera aussi qu'une attaque par réflexion+amplification peut être conduite avec un serveur DNS qui fait autorité, particulièrement si ce serveur sert des zones signées avec DNSSEC. Doit-on fermer les serveurs qui font autorité ? Quid des serveurs SNMP ouverts sans raison chez ces mêmes interlocuteurs ? Je passe sur les NTP ouverts par défaut chez Juniper, c'est cadeau.
  • Évidemment, il ne s'agit pas non plus de faire n'importe quoi puisqu'un récusif-cache ouvert est complice involontaire de l'attaquant en cela que c'est lui qui rend une attaque par réflexion+amplification intéressante (dissimulation relative de l'attaquant, amplification). Il y a tout une chaîne d'acteurs à protéger au-delà du réseau dans lequel se trouve le récursif-cache ouvert : les upstreams qui n'ont pas à assumer le choix, plusieurs victimes qui n'ont rien demandé, ... Si le réseau se fait blackholer par un opérateur, il sera également impossible pour les utilisateurs de ce réseau d'accéder aux services hébergés par cet opérateur ou derrière cet opérateur (downstreams). La mise en place de contre-mesures lors de la prise de décision de fournir un récursif-cache ouvert est donc une obligation morale envers autrui, à mon sens.
  • En conclusion pour ceux qui n'ont pas tout lu : ne pas confondre récursif ouvert par erreur/abandonné et récursif ouvert sciemment et surveillé. FDN n'a pas succombé à son récursif ouvert car il est surveillé et rate-limité (uniquement les requêtes portant sur le QTYPE ANY, à ma connaissance).

Typologie des attaques

Ce que j'ai observé, sur l'infra d'ARN, en presque un an :

  • Quel est le type de serveur DNS qui est le plus utilisé dans des attaques par réflexion+amplification, serveur qui fait autorité ou récursif ? Je n'ai vu aucune attaque sur notre serveur qui fait autorité (les requêtes pour vérifier s'il est un récursif ouvert ne comptent pas, of course). J'explique cela par le fait que nos zones sont trop peu intéressantes (elles ne sont pas signées, la requête la plus intéressante, de type ANY, a une taille de 71 octets au niveau 2 et génère une réponse de 337 octets) et inconnues comparées à zones intéressantes "bien connues" comme *.gov, isc.org, ... (à partir d'un scan réseau, il est possible de savoir qu'un récursif ouvert est adressé par telle IP mais pas de savoir quelles zones sert un serveur DNS qui fait autorité adressé par telle IP).
  • Requêtes de type ANY ou non ? Les deux et la proportion semble équivalente/se lisser sur une longue période : tantôt une écrasante majorité de requêtes de type ANY, tantôt une majorité de requêtes qui utilisent edns (pour pousser la limite de la réponse sur UDP au-delà des 512 octets) et qui portent sur de gros RRset A/AAAA (et une fois TXT), et aussi les deux en parallèle.
  • Régularité et intensité ? L'intensité est variable. Sans parler de fond permanent, il y a quand même quelques tests (es-tu un récursif ouvert ?) et plus si affinité (hop, je te fais passer quelques requêtes et je m'arrête) en quasi-permanence. Impossible de parler d'attaque à ce stade-là. À côté de cela, il y a les pics temporaires : 3/4 IPv4 sources différentes qui émettent en continu des requêtes qui génèrent des réponses supérieures à 4 ko, ce qui nous donne des pics de l'ordre de 100-140 kbps très régulièrement (de l'ordre d'au moins une fois par 24 heures). La durée de ces pics est extrêmement variable : plusieurs dizaines de minutes à quelques heures. On constate aussi un effet de rafales puis silence puis rafale que je ne m'explique pas dans le cadre d'une attaque... Le plus gros pic enregistré était de l'ordre de 350 kbps sur plusieurs jours. C'était dans les derniers mois de 2013, quand les attaques par réflexion+amplification étaient extrêmement à la mode. ÉDIT du 05/09/2014 à 16h45 : Nouveau pic à 700 kbps, ANY webpanel.sk, environ 4 ko au compteur, plusieurs dizaines d'IP sources réparties dans environ 20 /24, depuis plus d'une semaine (28 août - encore en cours aujourd'hui). Fin de l'édit. Au niveau de la régularité, je n'ai observé aucun schéma sur le long terme (pas plus la nuit que le jour (heure de Paris), les périodes de congés que les jours ouvrés, ...). Il peut se passer plusieurs jours sans attaque puis une déferlante de pics.
  • Domaines connus ou inconnus ? Après coup, je me suis rendu compte que cette métrique n'est pas pertinente car subjective (connu de qui ? Quelle portée géographique ? Connu selon quelle intensité ?). Je pensais pouvoir établir une typologie des domaines utilisés afin de faire de la limitation de trafic plus contraignante pour les domaines clairement conçus dans l'objectif de servir dans une attaque. Mais ça ne veut rien dire : cela peut-être un sous-domaine proposé par un hébergeur low-cost donc le site web donne une apparence légitime, cela peut conduire à un site web en langue étrangère voire à pas de site web du tout, ... Bref, impossible d'en déduire quoi que ce soit et de faire une classification (à partir d'un seul point de collecte, j'entends, si un même domaine est vu par plusieurs points de collecte, on peut avoir un doute légitime).
  • IPv4 ou IPv6 ? Les attaquants ne sont pas IPv6-ready, très clairement.

Cette typologie vaut ce qu'elle vaut puisque je l'ai réalisée sans méthodologie ni rigueur scientifique.

Comment procéder ?

Selon moi, il y a quelques étapes à suivre avant de pouvoir ouvrir un récursif DNS au monde entier l'esprit serein.

Adresse mail de contact valide

La première étape est de faire en sorte que l'on puisse vous contacter en cas de problème. Cela se fait à travers la base de données de votre RIR. Pour le RIPE, il suffit d'utiliser ce formulaire. En indiquant les adresses IPv4 et IPv6 que vous allez assigner à votre récursif ouvert, vous devez obtenir une adresse mail de contact. Évidemment, vous testerez cette adresse mail en vérifiant qu'elle est fonctionnelle et qu'elle pointe bien vers des personnes qui seront en capacité d'agir en cas de signalement. Si le formulaire vous retourne rien, vous avez quelques objets à créer dans la base de données de votre RIR.

Supervision et métrologie

La deuxième étape est de superviser la machine (physique, virtuelle, LXC, ...) qui sera votre récursif ouvert, pas tellement pour savoir si le serveur (logiciel) fonctionne, n'est pas planté, ... mais plutôt pour grapher le débit et le nombre de paquets sortants. Oui, c'est bien le trafic sortant qui est intéressant : quelle volumétrie de trafic ce serveur envoie-t-il à l'extérieur ?

L'objectif est triple :

  • Être averti d'une grosse attaque. L'outil de supervision doit lever une alerte, soit quand un seuil plutôt large est atteint sur une période très courte, de l'ordre de la minute (un pic important quoi), soit quand un seuil plutôt faible est atteint sur une période d'échantillonnage plus longue, de l'ordre des 5 minutes. Ma préférence va pour le deuxième type de seuil/échantillonnage car il indique qu'une volumétrie de trafic inhabituelle s'écoule depuis quand même une certaine période, ça serait bien d'aller y jeter un œil. Pour vous donner un ordre d'idée, chez ARN nous faisons un échantillonnage par 5 minutes et une alerte dès 250 kbps (1/4 de mbps).
  • Garder un historique grâce à la métrologie. À la fois pour avoir des éléments de réponse pour répondre aux mails d'abus alors que pourtant vous n'avez reçu aucune alerte (le cas s'est produit chez ARN, la victime a simplement envoyé un mail à tous les responsables des serveurs DNS récursifs qui l'attaquaient sans se préoccuper du débit effectif émis par chacun des récursifs) et pour avoir une trace visuelle, toujours plus expressive que des chiffres.
  • ÉDIT du 04/09/2014 à 19h00 : Calculer et grapher automatiquement le 95e centile. Très utile pour apprécier la facturation de vos transitaires. Vous pouvez alors raisonner en terme de coût : combien l'attaque en cours va-t-elle coûter et décider alors de fermer votre récursif-cache temporairement ou définitivement. Fin de l'édit

Cela peut se faire simplement avec votre outil de supervision/métrologie favori, Zabbix dans le cas d'ARN.

Ça peut aller jusqu'à utiliser Netflow/IPFIX ou des outils plus centrés sur le DNS comme DSC (chiffres, graphes, par QTYPE, remonter les QNAME populaires, ...).

On peut aussi créer des règles iptables "de comptabilité" : règles en « -j ACCEPT » qui permettent de comptabiliser (iptables -L -n -v pour voir les compteurs) les paquets entrants et sortants du port udp/53 de votre récursif.

Chez ARN, on s'est arrêté à Zabbix + compteurs iptables pour la prod'.

Préparer une configuration fermée

Dans la configuration de votre serveur DNS, prévoyez, en commentaire, le morceau de configuration qui ferme le récursif à votre réseau. L'idée est qu'en cas d'une sévère attaque, alors que vous serez totalement paniqués, vous puissiez facilement fermer votre récursif en attendant que ça se passe.

Cela a encore plus d'intérêt quand vous avez un moyen facile et permanent d'accéder à une console sur vos machines même si vos liens réseaux vers l'extérieur (transit, peering) sont saturés : accès physique ou accès distant (via IPMI en OOB, par exemple). Chez ARN, nous avons un accès IPMI à nos machines, pour information.

Choisir des méthodes pour juguler les attaques

RRL

Response Rate Limiting consiste à limiter le nombre de réponses par seconde que fournit un serveur DNS qui fait autorité à un préfixe IP(v4/v6) donné. C'est implémenté dans tous les logiciels serveur DNS qui font autorité : BIND >= 9.9.4 (pas activé à la compilation par défaut, 9.10 pour que ça soit le cas), nsd >= 3.2.15, knot, ...

Les avantages :

  • On est à un niveau très fin : quel couple QTYPE+QNAME dépasse le seuil défini ? Seuls les préfixes IP desquels émanent des requêtes portant sur ce couple seront rate-limitées. RRL s'adapte à l'attaque en cours, sans aucun travail humain puisque l'identification des paramètres de l'attaque (QNAME, QTYPE, sources) est automatique.
  • Une fois le seuil atteint, le serveur répondra quand même des réponses tronquées (un SLIP) invitant un vrai client DNS à retenter en TCP. Une réponse tronquée étant d'une taille inférieure, on annule l'effet d'amplification. Un client légitime dans un préfixe rate-limité aura quand même une réponse et ne sera pas totalement pénalisé.

Les inconvénients :

  • Les versions des logiciels serveur DNS qui implémentent RRL ne sont pas encore dans les dépôts stable de Debian. Pour BIND, la version 9.9.5 présente dans wheezy-backports a été compilé avec la fonctionnalité RRL. Rappel : la team Debian-security ne s'occupe pas des backports. 😉
  • On promène les paquets (requêtes) dans notre réseau pour finalement que le serveur refuse d'y répondre. Nuançons : ce n'est pas couteux puisque c'est en interne de notre réseau et que c'est les réponses qui ont un impact.
  • RRL est-il adapté pour les récursifs-cache ? Un récursif-cache n'a pas à interroger un serveur qui fait autorité des dizaines de fois par seconde pour un même QTYPE+QNAME grâce à son cache, ce qui justifie l'emploi de RRL sur les serveurs faisant autorité. Le client d'un récursif-cache en revanche n'a pas de cache donc il peut légitimement interroger le récursif-cache des dizaines de fois par seconde pour un même couple QTYPE+QNAME. Mon avis, basé sur les autres formes de limitation de trafic est qu'il suffit d'adapter le seuil. Mais peut-être que je me trompe sur ce point.

Chez ARN, cette solution n'a pas été retenue du fait qu'elle n'est pas implémentée dans les versions de logiciels serveur DNS disponibles dans les dépôts stable de Debian.

Limitation de trafic global en utilisant Netfilter

Puisque le routeur d'entrée d'ARN est une machine GNU/Linux (oui, un routeur ce n'est pas forcément un Cisco très cher 😉 ), on peut utiliser Netfilter pour limiter le trafic à destination de notre récursif-cache sur le port udp/53.

Pour la mise en pratique (pour le choix du module hashlimit, voir : Limiter le trafic d'un serveur DNS (notamment d'un récursif ouvert)) :

#IPv4
iptables -A FORWARD -o eth1.140 ! -s 89.234.141.0/24 -d 89.234.141.66/32 -p udp -m udp --dport 53 -j DNS-RATE-LIMIT
iptables -A DNS-RATE-LIMIT -m hashlimit --hashlimit-above 10/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name RL-DNS-GLOBL-v4 --hashlimit-srcmask 24 -m comment --comment "RATE-LIMIT ALL DNS QUERIES 10/s burst 20" -j DROP
 
#IPv6
ip6tables -A FORWARD -o eth1.140 ! -s 2a00:5881:8100::/40 -d 2a00:5881:8100:1000::3/128 -p udp -m udp --dport 53 -j DNS-RATE-LIMIT
ip6tables -A DNS-RATE-LIMIT -m hashlimit --hashlimit-above 10/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name RL-DNS-GLOBL-v6 --hashlimit-srcmask 56 -m comment --comment "RATE-LIMIT ALL DNS QUERIES 10/s burst 20" -j DROP

La création d'une chaîne nommée « DNS-RATE-LIMIT » vous semblera légitime dans la section suivante.

Ici, on autorise 10 requêtes DNS par seconde par /24 et par /56 avec un burst à 20 requêtes par seconde. Le burst signifie que le client a le droit à 20 requêtes dès la première seconde puis 10 requêtes par seconde pour les secondes suivantes. S'il n'atteint pas le seuil (10 requêtes par seconde donc), il récupère la possibilité d'atteindre 20 requêtes par seconde au bout de 2 secondes (combien de secondes faut-il pour atteindre 20 quand on en autorise 10 requêtes par seconde ?). Le contenu des hashtables sont consultables depuis /proc/net/ip*t_hashlimit/.

En fonction de l'organisation de votre infra, il est inutile de limiter le trafic émis par l'infra elle-même et les abonnés (VPS/VPN/ADSL) si vous avez déployé des contre-mesures pour lutter contre l'usurpation d'IP (BCP 38 + drop des paquets qui ont une de vos IPs en source en entrée de votre réseau). De plus, un FAI associatif a vocation à rester à taille humaine : on se connaît, on se fait confiance (et des hébergeurs low-cost bien connus sont plus pratiques et moins chers pour mener à bien une attaque). De plus, le récursif étant en local, les requêtes peuvent s'enchaîner plus vite donc le seuil peut-être atteint plus facilement. Exemples chez ARN : OpenVPN et son mécanisme d'iroute empêchent l'usurpation d'IP sur la partie VPN. Un VPS ne peut pas usurper une IP (qu'elle soit interne ou externe) car on a déployé BCP 38, Ingress Access Lists pour être précis, avec iptables, sur chaque interface réseau virtuelle.

On se rend compte que cette méthode impose un dilemme moral puisque plus on est permissif pour les usages légitimes, plus on est permissif envers les attaquants. J'ai choisi les seuils (10 r/s burst à 20) en fonction des exemples illustrant RRL ainsi qu'en fonction des exemples donnés par Stéphane Bortzmeyer (voir les ressources pointées au sommet de ce billet) puis j'ai ajusté. D'après mes tests, on ne peut pas descendre en dessous de ces valeurs sous peine de nuire à des usages légitimes : une installation Debian classique nous fait un pic à 12 r/s pendant l'installation (si la résolution échoue, l'utilisateur obtient une erreur et ne peut continuer), résolution des reverses dans un logiciel torrent, page web bien chargée sans utilisation d'Adblock/NoScript/autre, ...

Exemple récent de ce que permettent ces seuils dans la théorie : la requête ANY nrc.gov génère une réponse de 4,278 ko soit 34,224 kb. On considère 4 IP source différentes (cf typologie) dans 4 préfixes /24 différents en simultané : 136,896 kb. Chaque IP a le droit à 10 r/s en vitesse de croisière : 1368,96 kbps soit 1,33 Mbps. On est au niveau applicatif, il faut donc ajouter les entêtes IP+UDP (ce qui rendra le facteur d'amplification moins impressionnant mais on n'est pas la pour ça). Est-il moralement acceptable de laisser filer potentiellement plus de 1,33 Mbps de trafic pourri ? D'autant plus que les attaquants n'envoient pas un nombre constant de paquets mais agissent plutôt en mode grosse rafale, moins de paquets, grosse rafale, etc. Donc les 20 r/s du burst se débloquent assez régulièrement, ce qui permet donc à la grosse rafale de passer.

Le seul avantage que je vois à cette solution est, qu'une fois le seuil dépassé, les paquets en surplus provenant de préfixes limités sont supprimés en entrée du réseau.

Les inconvénients :

  • La limitation porte sur un préfixe (/24 en IPv4, /56 en IPv6). Il est évident que l'on joue sur la dispersion des utilisateurs de notre récursif dans des préfixes différents : si plusieurs utilisateurs viennent du même préfixe avec des usages "gourmands" sur une même période de temps, alors des usages légitimes seront bloqués avec les seuils présentés ci-dessus, c'est une certitude. Et un seuil plus permissif, même uniquement un burst plus élevé, permettra aux attaquants de générer encore plus de trafic pourri. Tout est histoire de compromis en sécurité informatique.
  • J'ai remarqué que les limitations présentées ci-dessus sont globalement inefficaces. Les règles iptables ne sont pas souvent déclenchées (un peu moins d'une centaine par semaine) car le nombre de paquets par seconde ne dépasse pas le seuil défini et car le burst se régénère vite (les deux sont liés). Notamment parce que des attaquants subtils utilisent plutôt un plus grand nombre de récursifs-cache couplé à un petit de requêtes par seconde destinées à chaque récursif-cache que l'inverse. J'en arrive à la conclusion que ces limitations protègent de gros chocs mais sont trop perméables aux attaques au quotidien.

Chez ARN, cette solution a été retenue mais elle ne suffit pas : on identifie assez clairement le besoin de limiter plus fermement certains QTYPE et QNAME.

Limitation de trafic portant sur le QTYPE ANY avec Netfilter

Une partie des attaques utilisent le QTYPE ANY. Ce type d'enregistrement est valide (au sens des normes donc on doit le laisser passer) mais sert très rarement dans la pratique sauf debug et logiciels plutôt mal conçus et désuets. On peut envisager de limiter plus fortement le trafic des requêtes DNS portant sur ce type.

La difficulté vient du fait que, dans le format d'un paquet DNS, le QTYPE arrive après le QNAME. On est donc dépendant de la taille du QNAME pour trouver le QTYPE dans le paquet. Il est donc impossible de limiter toutes les requêtes de type ANY en utilisant un langage tel que u32. On peut toutefois utiliser le module « string » de Netfilter qui fait une recherche linéaire.

Pour la mise en pratique (en complément de la section précédente, d'où la chaîne « DNS-RATE-LIMIT » 😉 ) :

#IPv4
iptables -I DNS-RATE-LIMIT -m string --algo bm --hex-string "|00 00 ff 00 01|" --from 28 -m hashlimit --hashlimit-above 1/sec --hashlimit-burst 2 --hashlimit-mode srcip --hashlimit-name RL-DNS-ANY-v4 --hashlimit-srcmask 24 -m comment --comment "RATE-LIMIT DNS ANY QTYPE 1/s burst 2" -j DROP
 
# IPv6
ip6tables -I DNS-RATE-LIMIT -m string --algo bm --hex-string "|00 00 ff 00 01|" --from 48 -m hashlimit --hashlimit-above 1/sec --hashlimit-burst 2 --hashlimit-mode srcip --hashlimit-name RL-DNS-ANY-v6 --hashlimit-srcmask 56 -m comment --comment "RATE-LIMIT DNS ANY QTYPE 1/s burst 2" -j DROP

Ici, on autorise 1 requête de type ANY par seconde par /24 et par /56 avec un burst à 2 requêtes par secondes.

Explications :

  • Concernant le motif recherché, « 00 00 ff 00 01 » : 00 : fin du QNAME, c'est-à-dire « . », la racine ; 00 ff : QTYPE, codé sur 2 octets (16 bits), 00 ff = 255 = ANY ; 00 01 : CLASS, codé sur 2 octets (16 bits), 1 = IN. Pour les numéros correspondant à chaque QTYPE/CLASS/autre, voir : Domain Name System (DNS) Parameters - IANA 😉 . On peut utiliser un motif plus court mais il faut alors faire attention aux faux positifs.
  • Concernant le début de la recherche dans un paquet, « --from 28/48 » : étant donné que ce module est plus gourmand que u32 (par exemple) car recherche linéaire et que le motif peut conduire à des faux positifs, je trouve que c'est une bonne idée de commencer la recherche au niveau applicatif, sans se soucier des couches inférieures. On prend la taille standard, sans s'occuper des options IP (v4 comme v6). Exemple : 20 octets IPv4 + 8 octets UDP. Le module string compte à partir de 0 (cf man) donc --from 28 : commence l'analyse au 29e octet donc au début de DNS. Le module string commence sa recherche au début de la couche IP, il ne faut donc pas comptabiliser la couche 2 (exemple : ne pas ajouter 14 octets pour Ethernet) ! Sur plusieurs sites web, j'ai vu l'usage de « --to » pour se prémunir contre d'immenses paquets qui rendraient la recherche très fastidieuse. C'est une mauvaise idée à mon avis : on ne peut pas prévoir l'emplacement du QTYPE ! Et si un paquet inclut des options IPv4 ou des entêtes IPv6 d'extension ? Et si le QNAME est très long ? Le QTYPE sera au-delà de la zone de recherche et le paquet échappera donc à notre filtre.
  • Attention à choisir des noms de hashtable différents en fonction de l'usage (rate-limit global ou rate-limit ANY). Utiliser une même table dans plusieurs règles fait que c'est la limite la plus basse qui sera appliquée MÊME si la règle correspondante n'est pas déclenchée ! Exemple : soit le jeu de règles de filtrage suivant (sans cumul avec les filtres présentés ci-dessus) :
    -A DNS-RATE-LIMIT -m u32 --u32 "0x0>>0x16&0x3c@0x14&0xffffff00=0xff00" -m hashlimit --hashlimit-above 1/sec --hashlimit-burst 2 --hashlimit-mode srcip --hashlimit-name RL-DNS --hashlimit-srcmask 24 -m comment --comment "RATE-LIMIT ANY ." -j DROP
    -A DNS-RATE-LIMIT -m u32 --u32 "0x0>>0x16&0x3c@0x14&0xffdfdfdf=0x3495343&&0x0>>0x16&0x3c@0x18&0xffdfdfdf=0x34f5247&&0x0>>0x16&0x3c@0x1c&0xffffff00=0xff00" -m hashlimit --hashlimit-above 1/sec --hashlimit-burst 2 --hashlimit-mode srcip --hashlimit-name RL-DNS --hashlimit-srcmask 24 -m comment --comment "RATE-LIMIT ANY isc.org" -j DROP
    -A DNS-RATE-LIMIT -m hashlimit --hashlimit-above 10/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name RL-DNS --hashlimit-srcmask 24 -m comment --comment "RATE-LIMIT ALL 10/s-20/s" -j DROP

    Les compteurs (iptables -L -n -v) des deux premières règles n'étaient pas incrémentés car les règles n'étaient pas déclenchées car les requêtes étaient de type A. Bizarrement, la dernière règle se déclenchait alors qu'une capture réseau montrait qu'on avait 4-8 requêtes/seconde mais jamais plus en entrée. En sortie de Netfilter, on voyait 1 à 2 requêtes par seconde. Vider les règles iptables et les remettre n'avait aucun effet. Après debug, le simple fait d'introduire l'une des deux premières règles faisait dysfonctionner la dernière règle. Utiliser les mêmes règles mais avec des noms de tables différents a résolu ce problème que je n'explique pas.

L'avantage est que ces limitations, complémentaire aux limitations "globales" sont efficaces : déclenchées beaucoup plus souvent (un peu plus de 8 millions de paquets en une semaine) sans pour autant empêcher les usages légitimes.

Les inconvénients :

  • Quid des faux positifs que peut provoquer le motif recherché ? Le risque est très limité puisque ces recherches s'appliquent uniquement sur la charge utile DNS des paquets à destination du port udp/53 de notre récursif. FDN n'a pas rencontré de problèmes, à ma connaissance.
  • Une recherche linéaire permet-elle d'encaisser de grosses attaques ou le netfilter sur notre routeur d'entrée va devenir un goulot d'étranglement ? Je n'ai pas réussi à mettre en avant quoi que ce soit avec mes tests dans un lab' local (100 mbps). Peut-être que je m'y suis mal pris.
  • Ces limitations apportent une solution à une partie du problème seulement : quid des attaques qui portent sur un gros RRset de type autre que ANY ?

Chez ARN, cette solution a été retenue, en complément d'une limitation globale du nombre de requêtes par seconde et apporte de bons résultats.

Limitation de trafic portant sur des QNAME précis avec Netfilter

On peut être tenté de limiter plus fortement le trafic des requêtes portant sur des QNAME clairement identifiés comme servant dans des attaques par réflexion+amplification. La première question est de savoir comment on identifie les QNAME en question. Stats avec DSC ? Un dump réseau (quid alors de la vie privée des vrais utilisateurs (oui, avec un petit nombre d'abonnés, on peut se souvenir, de tête, à quel abonné on a donné quelle IP et donc voir des choses qui ne nous regardent pas)) ? Se baser sur des sites web comme DNS Amplification Attacks Observer ou Honeypot DNS and amplification attacks ?

Pour la mise en pratique, on utilise les modules u32 et hashlimit de Netfilter.

L'avantage est que cette méthode est très efficace, surtout quand vous ajoutez préventivement les QNAME que vous trouvez sur les sites web sus-cités.

Les inconvénients :

  • Quelle confiance accorder aux sites web qui répertorient les attaques ? Nuançons : on peut très bien construire nos propres stats avec DSC.
  • Comment entretenir le jeu de règles ? D'un côté, trop de règles ralentissent Netfilter, de l'autre des règles cessent d'être utiles voire ne le sont jamais (toutes les attaques recensées sur les sites web sus-cités n'arrivent pas jusqu'à notre récursif). Comment purger les règles automatiquement ? Quelle fréquence ? Quels critères ? Quid des QNAME qui cessent d'être utilisés temporairement et reviennent en force des mois plus tard (exemple : fkfkfkfa.com) ? Bref, l'entretien est loin d'être simple.
  • Le suivi des nouvelles attaques, la création/l'application des règles u32 et l'entretien du jeu de règles ont un coût humain qui ne semble pas compatible avec l'associatif : le temps humain est précieux, aucune motivation pour faire ce boulot, ...

Chez ARN, j'ai longuement testé (et apprécié) cette méthode avant de me rendre compte que ce n'était pas compatible avec le rythme associatif et de laisser tomber.

Limiter la taille des réponses que notre récursif-cache fera sur UDP

Avec l'extension edns0, un client DNS a la possibilité d'indiquer à un serveur DNS qu'il veut recevoir une réponse plus large plus que les 512 octets par défaut sur UDP. Le serveur peut ainsi s'adapter. Le serveur possède également une directive de configuration pour indiquer quelle est la taille maximale d'une réponse qu'il acceptera d'envoyer sur UDP, quelle que soit la valeur indiquée par le client dans sa requête. Pour BIND et Unbound (à partir de la version 1.4.21), il s'agit de « max-udp-size [1024-4096] » avec une valeur par défaut et max impératif fixés à 4096 octets. ÉDIT du 04/09/2014 à 18h45 : Unbound n'impose pas de minimum, contrairement à BIND. Fin de l'édit

Pourquoi ne pas utiliser ces directives de configuration pour limiter les attaques qui utilisent de gros RRset d'un type autre que ANY et edns ? Ainsi le facteur d'amplification est réduit et notre récursif-cache intéressera beaucoup moins les attaquants.

ÉDIT du 05/09/2014 à 17h30 : Si le client de notre récursif-cache est un stub-resolver (« un résolveur qui ne sait pas suivre les renvois et qui dépend donc d'un ou de plusieurs résolveurs complets pour faire son travail [...] C'est ce résolveur minimum qu'appelent les applications lorsqu'elles font un getaddrinfo() ou getnameinfo(). Sur Unix, le résolveur minimum fait en général partie de la libc et trouve l'adresse du ou des résolveurs complets dans /etc/resolv.conf » ), alors il ne fait pas de validation DNSSEC donc il ne positionne pas le bit DO et donc notre récursif ne lui enverra pas les signatures cryptographiques (qui sont quand même ce qui fait exploser la taille des réponses loin devant les IDN et IPv6). En conséquence, les réponses seront petites. S'il s'agit d'un récursif-cache qui forward les requêtes à notre récursif (et à d'autres, ce qui permet de switcher sans dommage pour l'utilisateur final si notre limite est trop basse et impose un passage à TCP pour une requête donnée), alors une limite de la taille des réponses plus petite que celle par défaut impactera possiblement uniquement la première résolution (d'un même nom) d'un client de ce récursif-cache. Les résolutions suivantes de ce nom seront immédiates puisque mises en cache.

De plus, avant d'être obligé de tronquer sa réponse, un récursif-cache peut "produire" une section additionnelle minimaliste ce qui a pour effet de réduire la taille de la réponse, de l'amener en dessous de la limite et donc de fournir une réponse non tronquée au client (la section « ANSWER » de la réponse est intacte).

Face à ces optimisations, la taille maximale des réponses par défaut semble démesurée.

Un usage plus important de TC + passage en TCP ne sera pas toujours pénalisant : la norme autorise l'utilisation de connexions TCP persistantes. Les récursifs-cache implémentent cela. Malheureusement, aucun stub-resolver ne supporte cela à l'heure actuelle.

La question qui reste en suspens pour ma part est : comment mesure-t-on si notre limite est trop basse et gêne les utilisateurs de notre récursif ? Mesurer la proportion de trafic TCP versus UDP ? Et si le client (stub-resolver ou récursif qui forward) n'a pas réessayé suite à l'envoi d'une réponse tronquée mais a contacté le prochain récursif-cache dans sa configuration ? Un récursif-cache qui envoi uniquement des réponses tronquées (ce qui est loin d'être le cas avec une limite à 1460 octets à l'heure actuelle) est un récursif-cache plutôt inutile, à mon avis. Fin de l'édit.

À titre personnel, je ne vois aucun avantage à cette méthode. ÉDIT du 05/09/2014 à 17h10 : Après avoir examiné à nouveau cette piste, je vois plusieurs avantages :

  • Ça permet de couper net dans le tas. Illustration avec l'attaque "populaire" (du point de vue du récursif d'ARN) du moment : ANY webpanel.sk, environ 4 ko au compteur, plusieurs dizaines d'IP source réparties dans environ 20 * /24 différents. En laissant la limite par défaut (4096 octets) : 700 kbps en sortie de notre récursif au 95e centile sur une période de 7 jours. En passant la limite à 1460 octets : 180 kbps au 95e centile sur les 39h écoulées depuis la reconfiguration. Évidement, cela vient en sus de l'utilisation de Netfilter pour limiter le nombre de requêtes par seconde portant sur le QTYPE ANY.
  • Cette technique est la seule (pour l'instant, RRL permet aussi cela) à pouvoir contrer les attaques de type edns0 + gros RRset de manière automatisée puisque nous avons vus ci-dessous que les autres méthodes sont soit inefficaces (rate-limiting global) soit manuelles (rate-limiting sur des QNAME précis).

Fin de l'édit.

Les inconvénients :

  • On pénalise tout le monde (alors que la limitation de trafic ne s'applique qu'à certains préfixes), tout le temps (alors que la limitation de trafic s'applique temporairement et uniquement en cas d'abus), pour tous les usages (ici même une réponse légitime signée avec DNSSEC pourra être tronquée). Certes, un client DNS légitime réessayera en TCP mais on ralentit fortement les usages.
  • Quelle est la taille idéale à autoriser ? Celle qui ne pénalise pas les usages ? Comment l'identifie-t-on sans être intrusif ni dépendre de notre typologie d'utilisateur ? +/- la MTU pour éviter l'overhead causé par la fragmentation en cas d'attaque ? Quelle est la taille d'une réponse DNSSEC standard ? Qu'est-ce qu'une réponse signée avec DNSSEC standard ? Cette limite peut évoluer avec le temps/la population d'utilisateurs, il faut donc prévoir de l'adapter.

Chez ARN, cette idée n'a pas dépassé le stade du test dans un lab'. ÉDIT du 05/09/2014 à 17h05 : Une limitation de la taille des réponses sur UDP à 1460 octets est en test depuis le 04/09/2014. Fin de l'édit. ÉDIT du 04/11/2014 à 11h00 : Avec un recul de deux mois : ça juste marche. Les usages légitimes ne semblent pas être pénalisés et les attaques de forte amplitude ont cessé (on est à quelques kbps au 95e centile sur un mois). Peut-être les attaquants mesurent le gain d'amplification obtenu et observent que notre récursif-cache est un mauvais élève de ce point de vue. Il convient de rester prudent pendant encore quelques mois : les attaques par réflexion+amplification vont et viennent en terme de popularité donc il ne faut pas crier victoire trop vite. Fin de l'édit.

ÉDIT du 04/09/2014 à 22h00 : On peut aller encore plus loin et ouvrir un récursif-cache sur l'extérieur uniquement en TCP. Malheureusement, c'est encore du domaine du futur et ce, pour plusieurs raisons :

  • Les stub-resolvers (la glibc, utilisée, entre autres, par Debian, en tous cas), n'utilisent pas spontanément TCP mais tentent d'abord de faire leur requête sur UDP puis, en cas d'obtention d'une réponse tronquée seulement, retentent sur TCP. Il n'y a aucune directive de configuration à indiquer dans resolv.conf/host.conf pour forcer ce cas d'usage. Étant donné qu'un récursif-cache a vocation à avoir une majorité de stub-resolvers comme clients, la solution d'un récursif-cache répondant uniquement sur TCP n'est pas envisageable à l'heure actuelle. ÉDIT du 22/02/2015 à 13h55 : Depuis sa version 2.14, la glibc dispose de l'option « use_vc » (à utiliser dans resolv.conf, « options use_vc »). Cette option est disponible depuis Debian Jessie. OpenBSD a l'équivalent « tcp ». Les autres implémentations (FreeBSD, NetBSD, DragonFlyBSD, bionic (Android, Firefox OS), libresolv (Mac OS X), uclibc) ne disposent pas encore d'une telle option, ce qui en limite l'impact/l'intérêt. Source : Recursive DNS over TLS over TCP 443. Fin de l'édit.
  • Les utilisateurs de notre récursif-cache peuvent être dans des réseaux nazis dans lequels les administrateurs croient encore que DNS c'est uniquement udp/53 et bloquent donc le port tcp/53 (volontairement avec un pare-feu ou involontairement en utilisant des middleboxes mal-conçues). Ces réseaux ne sont pas rares ! Même sans évoquer notre cas d'usage, ces configurations poseront problèmes de toute façon et disparaîtront à terme.
  • TCP est plus lent (en cas d'absence de support des connexions TCP persistantes) et plus coûteux en ressources côté récursif-cache. Lire : Le DNS va t-il utiliser de plus en plus souvent TCP ?.

Fin de l'édit

Tester vos contre-mesures

Je n'ai pas été cherché bien loin les outils que j'ai utilisés.

Dans le lab' :

Sur la prod' :

  • Une bête boucle shell :
    for i in {1..<à adapter en fonction du seuil de la règle iptables à tester>}; do dig @<serveur v4 et v6> +short +retry=0 +time=1 ANY example.net.; [date;] done

    « time » avec une valeur d'une seconde permet de voir clairement le déclenchement d'une règle iptables. Cette simple boucle permet de vérifier que la limitation s'applique sans pour autant bourriner. Je trouve le résultat particulièrement lisible : burst puis vitesse de croisière. ÉDIT du 04/09/2014 à 21h15 : Je n'utilise pas « repeat » pour illustrer cet article car je suis un faible qui utilise bash, donc je n'ai pas cette commande pourtant built-in dans d'autres shells. Fin de l'édit

  • Une fois la vérification de l'efficacité de vos règles iptables effectuée, il vous faudra des beta testeurs pour tester en conditions réelles/normales que la limitation ne provoque pas de faux positifs.

Problèmes rencontrés

  • J'ai constaté un manque flagrant de documentation sur « comment monter un serveur DNS récursif-cache ouvert mais surveillé ». Même ceux qui le font, comme DNS-OARC ne sont pas bavards à ce sujet. Effet de dissuasion pour éviter les mauvaises manipulations et les nuisibles ? Idem, j'aimerais bien avoir des feedbacks sérieux sur quelle volumétrie de trafic "pourri" il est moralement acceptable de laisser filer sur les Internets.
  • Mettre en place un serveur DNS récursif-cache demande de la patience. Simplement car il faut du temps pour tâter le terrain, trouver les bonnes méthodes pour tenter de juguler les attaques, ajuster les paramètres du rate-limiting, obtenir des feedbacks, ... C'est un travail de longue haleine.
  • L'obtention de feedbacks est elle-même compliquée. D'abord car il est parfois difficile d'établir un lien de causalité entre un problème constaté et les mesures de limitation du trafic prises (un « could not resolve ... » est parlant mais ce n'est pas toujours ce type de message que l'on rencontre). Ensuite car les gens ne signalent pas forcément qu'ils rencontrent un problème avec le récursif (« c'est peut-être un problème temporaire ? problème de réseau ? »). En sont-ils simplement conscients ? Plusieurs entrées dans le fichier /etc/resolv.conf ou l'utilisation d'un serveur DNS forwarder en local suffisent à masquer le problème (basculement sur d'autres serveurs récursifs, cache, ...). Bref, prêtez attention à la configuration des machines de vos testeurs.

Merci à Stéphane Bortzmeyer pour sa relecture attentive, mais cela ne veut pas dire qu'il est d'accord avec tout (notamment sur mon absence d'intérêt pour une réduction de la taille max des réponses sur UDP).

Les commentaires sont fermés