Blocage d’attaque par amplification DNS avec fail2ban et Blocky

Bonjour à tous,

Spirio propose via le domaine dns.spirio.fr un accès ouvert à un résolveur DNS basé sur le logiciel Blocky et qui inclut des restrictions de domaines afin de protéger les utilisateurs des publicités, malwares et de la pornographie. Le problème avec les mesures de protection est qu’elles peuvent et vont souffrir de faux positifs. Il appartient alors à l’administrateur de ces solutions de superviser leur usage afin de les détecter et de réduire leur nombre.

Détections d’anomalies dans les journaux

C’est lors d’une recherche d’autorisation d’un domaine que je suis tombé sur des requêtes DNS qui m’ont interrogés. Ces requêtes étaient de type ANY et de type TXT et étaient présentes en très grands nombres dans mes journaux.

  • Type ANY (« Tous ») : Les requêtes de type ANY demandent au serveur DNS de retourner toutes les informations qu’il a sur un domaine donné. Cela inclut toutes les entrées DNS, telles que les enregistrements A, AAAA, MX, NS, et bien d’autres. Étant donné que cette requête demande un grand nombre d’informations, elle est souvent utilisée pour amplifier le trafic dans le cadre d’une attaque par amplification.
  • Type TXT (« Texte ») : Les enregistrements TXT sont utilisés pour stocker des données textuelles arbitraires dans les enregistrements DNS. Les attaquants peuvent manipuler ces enregistrements TXT pour inclure des données volumineuses, telles que des chaînes de caractères aléatoires ou des blocs de texte inutiles. Lorsqu’une requête TXT est envoyée avec une taille d’enregistrement texte exagérée, elle peut générer une réponse beaucoup plus importante que la requête elle-même.

Le nombre important de requêtes et la similitude des adresses IP source m’ont amené à conclure que le résolveur DNS que je mets à disposition était utilisé comme amplificateur DNS.

Attaque DNS par amplification

Une attaque DNS par amplification exploite la nature du DNS pour multiplier de manière significative le volume du trafic réseau, tout en cachant l’origine de l’attaque. Le déroulé d’une attaque DNS par amplification est le suivant :

  1. L’attaquant envoie de fausses requêtes DNS de type ANY ou TXT à un grand nombre de résolveurs DNS publics, en usurpant l’adresse IP de la cible.
  2. Les résolveurs DNS, croyant qu’ils répondent à la cible réelle, génèrent des réponses volumineuses contenant toutes les informations disponibles sur le domaine ciblé.
  3. Ces réponses volumineuses sont renvoyées à la cible réelle, provoquant une saturation de sa bande passante et une interruption de service.

En complément, cela a aussi pour conséquence d’atteindre à la réputation de l’adresse IP de mon serveur.

Tentative de remédiation avec Fail2ban

Configuration de fail2ban

Afin de bloquer les nombreuses requêtes DNS de type ANY que je recevais, j’ai pensé à utiliser fail2ban afin de les bloquer. La mise en place d’une nouvelle règle dans fail2ban fonctionne de la façon suivante :

  • Création d’une règle dans le répertoire /etc/fail2ban/rules.d/ ;
  • Chargement de la règle dans jail.conf ;
  • Redémarrage de fail2ban ;
  • Vérification de la présence de la règle de blocage dans nftables.

Prenons l’exemple de cette requête DNS pour dérouler le tutoriel :

May 29 11:42:00 vps-xyz blocky[28185]: 8mA== client_ip=185.173.x.y client_names=185.173.x.y hostname=blocky-hostname question_name=sl. question_type=ANY response_code=NOERROR response_reason=CACHED response_type=CACHED

Création d’un fichier /etc/fail2ban/rules.d/blocky-dns.conf qui contient la règle fail2ban à appliquer.

[Definition]
failregex = .* client_ip=<HOST>.* question_name=sl\. question_type=ANY
ignoreregex =

Il faut ensuite ajouter une jail qui utilisera cette règle. On va pour cela modifier le fichier /etc/fail2ban/jail.d/jail.local et l’ajouter. Je me suis un peu amusé à mettre des durées et des déclenchements spécifiques. La règle de blocage autorise jusqu’à 5 occurrences de la règle précédente dans les 10 dernières minutes avant d’appliquer le bannissement de l’adresse IP à tous les ports pendant 10 minutes. Si la règle de filtrage s’active à nouveau, la durée du bannissement se voit multiplié par 1.5 à laquelle s’ajoute une durée aléatoire inférieur à 10 heures.

[blocky-dns]
enabled = true
filter = blocky-dns
backend = systemd
maxretry = 5
bantime = 600
findtime = 600
bantime.increment = true
bantime.factor = 1.5
bantime.rndtime = 36000
banaction = nftables-allports

On va ensuite redémarrer fail2ban afin que la nouvelle règle de banissement soit prise en compte.

sudo systemctl restart fail2ban

On vérifie ensuite que la règle est bien activée.

sudo fail2ban-client status

Des résultats insatisfaisants

Malgré l’efficacité de la règle, utiliser fail2ban comme outil de protection des attaques par amplification DNS ne me semble pas être une bonne idée.

  • Je bloque l’accès aux services de mon serveur aux adresses IP sources alors qu’elles sont aussi victimes ;
  • Les filtres fail2ban sont spécifiques et peuvent être abusés et contournés ;
  • Les journaux générés par les requêtes sont verbeux et prennent de la place ;
  • L’anonymat des requêtes DNS reçues n’est plus maintenu.

Et avec Blocky

La solution idéale serait d’imposer un quota par type de requête DNS par adresse IP. Malheureusement, Blocky ne propose ni cette option pour certains type de requête DNS ni même l’option de quota. La solution que j’ai trouvé consiste à refuser de répondre aux requêtes à toutes les requêtes DNS de type ANY et TXT. J’ai également supprimé les règles fail2ban que j’avais mises en place, car inadaptées.

La désactivation des réponses pour les requêtes DNS de type ANY et TXT a malheureusement pour conséquence d’empêcher d’accéder à ces champs. Cela peut poser problème pour le champs TXT car celui-çi est notamment utilisé pour la Sécurité et validation SPF (Sender Policy Framework) ainsi que pour DNSSEC (DNS Security Extensions).

Conclusion

Même si la solution que j’ai trouvée n’est pas parfaite, elle m’a permis d’arrêter l’utilisation du résolveur DNS comme serveur d’amplification.

L’absence d’option de configuration de Blocky pour répondre à ce type de menace confirme qu’il n’est pas adapté pour être rendu accessible sur internet. C’est d’ailleurs pour cela qu’il porte dans son descriptif : Blocky is a DNS proxy and ad-blocker for the local network written in Go.

Avant de changer de résolveur DNS, je suis ouvert à la mise en place de meilleures solutions.


Commentaires

12 réponses à “Blocage d’attaque par amplification DNS avec fail2ban et Blocky”

  1. Je viens de tester et, question faux positif, c’est en effet pas mal, mon propre domaine, http://www.bortzmeyer.org, est bloqué.

    % dig @dns.spirio.fr http://www.bortzmeyer.org AAAA

    ; <> DiG 9.18.24-0ubuntu0.22.04.1-Ubuntu <> @dns.spirio.fr http://www.bortzmeyer.org AAAA
    ; (1 server found)
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50418
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0

    ;; QUESTION SECTION:
    ;www.bortzmeyer.org. IN AAAA

    ;; Query time: 32 msec
    ;; SERVER: 54.37.234.80#53(dns.spirio.fr) (UDP)
    ;; WHEN: Fri Jul 19 15:36:34 CEST 2024
    ;; MSG SIZE rcvd: 36

    1. Bonjour Stéphane,

      Merci pour ton commentaire. dns.spirio.fr ne fonctionnait que pour l’IPV4. Je viens d’activer la résolution des noms pour l’IPV6.

      Je l’avais désactivé car je n’en avais pas l’usage mais c’est un DNS ouvert. Je vais surveiller pour voir s’il n’y a pas d’abus.

  2. Bonjour,

    honnetement, je ne suis pas sur qu’un open resolver sur internet soit une bonne idée.

    A minima, je suggere de :
    1) bloquer les ANY
    2) forcer TCP en répondant systématiquement TRUNCATE aux requêtes UDP
    2bis) ou n’autoriser que DoH/DoT
    3) du rate limiting un peu agressif pour embêter les petits malins qui voudraient faire du dns tunnelling.

    Pour un lab, c’est assez efficace.

    Enfin, je ne connais pas blocky mais la presentation indique « Blocky is a DNS proxy and ad-blocker for the local network written in Go ». Est ce bien approprié ?!

    1. Bonjour Max,

      Il faut parfois faire des essais même si l’on n’est pas convaincu. Merci pour ton commentaire et tes suggestions, je vais y réfléchir.

  3. Perso, sur mon serveur DNS ouvert, j’ai subi les millions de requêtes dont tu parles. J’ai fini par faire un script maison en Python. Le script parse à l’aide de regex un fichier JSON contenant le journal de requêtes d’AdGuard (via ADGUARDQUERY, disponible sur GitHub : https://github.com/Gamerou/Adguard-Live-Query-Log) et en extrait les adresses IP. Il permet de détecter les adresses IP qui effectuent un grand nombre de requêtes de type TXT (et toute autre requête souhaitée) et de les ajouter à une liste de blocage si elles dépassent un certain seuil. Le script extrait les premiers groupes de chiffres des adresses IP (car les attaquants utilisent souvent des dizaines d’IP du même range). En revanche, il exclut certaines adresses IP spécifiques, notamment celles locales, celle du serveur, et celle de mon reverse. Ensuite, il compte le nombre d’occurrences des adresses IP qui effectuent des requêtes de type TXT. Si une range d’adresse apparaît 15 fois ou plus, elle est ajoutée à une liste de blocage via IPSET. Le script fonctionne dans une boucle infinie, récupérant les journaux de requêtes toutes les 8 secondes et répétant le processus d’analyse. Le trie se fait vraiment très rapidement du coup. Et depuis, plus de soucis =D
    Si ça peut aider…. https://pastebin.com/f7NYTYf5 ce qui est banni via IPSET (ipset create spamlist hash:ip timeout 0). Exemple de la sortie du script : https://pastebin.com/RYvX0whn

    1. Bonjour Karesh,
      Merci pour ton commentaire et les explications. Le script Python est une excellente idée ! Est-ce que tu pourrais me le transmettre Python ?

      1. Re,

        Voici les liens vers les scripts :

        Le script Python : http://overslek.com/temp/lecture.py
        Le script PHP AdguardQuery : http://overslek.com/temp/adguardquery.php

        Je pense qu’il y a certainement des possibilités d’amélioration et d’optimisation, mais pour mon usage, ça fonctionne bien. Ce script bannit les plages IP avec plus de 15 IP différentes ainsi que les IP des pays de type CN.

        En complément, j’ai ajouté une règle BURST IPTABLE et j’ai constaté une différence immédiate. Le bannissement des plages IP basé sur XXX.XXX peut sembler extrême, mais pour mon utilisation, cela suffit. Je suis passé de 15 millions de requêtes à 10 000 requêtes legit journalières.

        Si tu veux voir les IP qui ont déjà été bannies : http://overslek.com/temp/spamlist.txt

        Si tu as besoin de plus d’informations, tu peux me trouver sur Discord : overslek 🙂

  4. Petite update : http://overslek.com/temp/lecture.html

    # MISE A JOUR DU 26/07
    # MODIFICATION maxelem en 80000000
    # CREATION D’UNE NOUVELLE SPAMLIST AUTOMATIQUEMENT SI PLEINE

    Modification du limit à 100000 dans adguardquery.php,

    J’évolue en même temps que toi, car je suis confronté depuis récemment.

    J’espère que tu as pu faire quelque chose de ce script, n’hésite pas à me tenir au courant 🙂

    1. J’ai creusé un peu plus la solution fail2ban car cela me semble plus maintenable mais je n’arrive pas à obtenir ce que je veux.

      Identifier les /24 qui spam et les bannir,car j’ai des tentatives uniques en masse.

      Je pense que je vais aller voir du côté de ton script plus en détail.

  5. Si tu veux qu’on en parle, et si tu veux de l’aide, n’hésite pas à me donner un contact, tu as mon discord plus haut 🙂

    1. J’ai fait un script qui répond quasiment parfaitement à mon besoin. Il détecte les requêtes de type MX pour des /20, dès que le seuil de 5 pour 10 secondes est dépassé, il bloque les /20 pendant 5 minutes.
      J’ai réduit de 95% le bruit. Si cela t’intéresse, voici le lien pour le script : https://pastebin.com/VN5Uhmqp

      Je ferai un article dessus pour détailler un peu le raisonnement et la méthodologie.

  6. On est jamais mieux servi que par soi même, c’est quand même dommage que ce genre de script ne soit pas directement intégré …