InfraFenceInfraFence

Live Security Operations

Commencer

Comment détecter le tunneling DNS sur un serveur Linux

Dernière mise à jour:

Le tunneling DNS cache des données dans des requêtes DNS. Un programme du serveur demande des noms comme mzxw6ytboi4dgnzt.tunnel.example, où le sous-domaine est la donnée, et le serveur de noms de l'attaquant pour tunnel.example répond avec d'autres données dans des enregistrements TXT ou autres. Cela fonctionne parce que presque tous les serveurs ont le droit d'envoyer des requêtes DNS, même ceux dont le trafic sortant est par ailleurs filtré. Les malwares s'en servent pour recevoir des commandes et faire sortir des données volées ; des outils comme iodine et dnscat2 construisent des connexions réseau entières par ce canal.

Ce trafic laisse des traces que le DNS ordinaire ne laisse pas : des sous-domaines très longs et aléatoires, de nombreux noms uniques sous un même domaine, une forte part de requêtes TXT, un rythme régulier de requêtes vers un seul domaine. Ci-dessous : comment capturer et lire le trafic DNS du serveur, vérifier les logs du résolveur, trouver le programme et bloquer le tunnel. Les commandes sont pour Debian et Ubuntu et pour la famille RHEL.

Savoir à quoi ressemble un tunnel DNS

SigneTrafic normalUn tunnel
Longueur des sous-domainesDes mots courts : www, mail, api30 à 63 caractères aléatoires par label, souvent plusieurs labels
Noms uniques par domaineQuelques-uns, demandés encore et encoreDes centaines ou des milliers, chacun demandé une fois
Types de requêtesSurtout A et AAAABeaucoup de requêtes TXT, NULL, CNAME ou MX
RythmeDes pics quand quelqu'un utilise un serviceUn débit régulier, jour et nuit, vers le même domaine
RésolveurCeux de /etc/resolv.confParfois un serveur DNS que le système n'est pas configuré pour utiliser

Remarque: Certains trafics légitimes y ressemblent : les recherches des antivirus et des services de réputation (qui encodent des empreintes de fichiers dans les noms), les vérifications de listes noires d'un serveur de messagerie, et certains noms de CDN et de clouds. Vérifiez le domaine avant de parler de tunnel.

Capturer le trafic DNS du serveur avec tcpdump

Installez tcpdump, et tshark pour l'analyse plus bas. Sur Debian et Ubuntu, l'installation de tshark demande si les utilisateurs non root peuvent capturer : répondez non.

Debian / Ubuntu
sudo apt install tcpdump tshark
RHEL, Rocky Linux, AlmaLinux, Oracle Linux, Fedora
sudo dnf install tcpdump wireshark-cli

Enregistrez cinq minutes de trafic DNS, UDP et TCP, sur toutes les interfaces. Avec systemd-resolved, les programmes interrogent le stub local sur 127.0.0.53 et le stub interroge l'amont : -i any voit les deux.

Toutes distributions
sudo timeout 300 tcpdump -i any -nn -Z root -w /root/dns.pcap 'port 53'

Lisez-le ensuite. D'abord la part de chaque type de requête : beaucoup de TXT? ou de NULL? saute aux yeux.

Toutes distributions
sudo tcpdump -nn -r /root/dns.pcap 'dst port 53' 2>/dev/null \
  | grep -oE '[A-Z0-9]+\? [^ ]+' | awk '{print $1}' | sort | uniq -c | sort -rn

Puis les domaines qui ont le plus de noms uniques. Un tunnel place un domaine en tête avec des centaines de noms ; le trafic normal se répartit sur de nombreux domaines avec peu de noms chacun. (Le décompte regroupe par les deux derniers labels : example.co.uk apparaît comme co.uk.)

Toutes distributions
sudo tcpdump -nn -r /root/dns.pcap 'dst port 53' 2>/dev/null \
  | grep -oE '[A-Z0-9]+\? [^ ]+' | awk '{print tolower($2)}' | sort -u \
  | awk -F. '{n = NF - 1; if (n >= 2) print $(n-1) "." $n}' | sort | uniq -c | sort -rn | head -20

Et les noms qui ont un label de 30 caractères ou plus :

Toutes distributions
sudo tcpdump -nn -r /root/dns.pcap 'dst port 53' 2>/dev/null \
  | grep -oE '[A-Z0-9]+\? [^ ]+' | awk '{print $2}' \
  | awk -F. '{for (i = 1; i < NF; i++) if (length($i) >= 30) {print; break}}' | sort -u | head -20

La même chose avec tshark

tshark donne des statistiques par type de requête, la source et la destination de chaque requête, et trouve les réponses anormalement grandes, que les tunnels utilisent pour faire revenir des données :

Toutes distributions
sudo tshark -r /root/dns.pcap -q -z dns,tree
sudo tshark -r /root/dns.pcap -Y 'dns.flags.response == 0' -T fields \
  -e ip.src -e ip.dst -e dns.qry.type -e dns.qry.name | head -50
sudo tshark -r /root/dns.pcap -Y 'dns.flags.response == 1 && udp.length > 512' -T fields \
  -e dns.qry.name | sort | uniq -c | sort -rn | head

Remarque: Ceci ne voit que le DNS sur le port 53. Un tunnel sur DNS over HTTPS circule comme du HTTPS ordinaire sur le port 443 et reste invisible ici ; DNS over TLS utilise le port 853, que l'étape 5 bloque.

Vérifier les logs du résolveur

Regardez d'abord quels résolveurs le serveur est configuré pour utiliser. Des requêtes vers toute autre adresse méritent à elles seules un examen.

Toutes distributions
cat /etc/resolv.conf
resolvectl status 2>/dev/null | grep -E 'DNS Servers|Current DNS'

systemd-resolved ne garde pas de log des requêtes, mais depuis systemd 252 (Debian 12, Ubuntu 24.04, RHEL 9 et ultérieurs) il affiche chaque résolution en direct. Laissez-le tourner quelques minutes et guettez les noms longs et aléatoires :

systemd-resolved
sudo resolvectl monitor

Si le serveur fait tourner son propre résolveur, activez son log des requêtes. Pour unbound, puis sudo systemctl restart unbound :

unbound
# /etc/unbound/unbound.conf (RHEL) or a file in /etc/unbound/unbound.conf.d/ (Debian, Ubuntu)
server:
    log-queries: yes
    log-replies: yes
BIND
sudo rndc querylog on

Les deux écrivent dans le journal. Cette commande liste les noms qui ont un label de 30 caractères ou plus sur la dernière heure :

Toutes distributions
sudo journalctl --since "1 hour ago" -u unbound -u named -u bind9 \
  | grep -ioE '([a-z0-9-]{30,}\.)+[a-z0-9.-]+' | sort | uniq -c | sort -rn | head -20

Attention: Les logs de requêtes grossissent vite sur un résolveur chargé. Désactivez-les une fois terminé (log-queries: no, sudo rndc querylog off).

Trouver le programme derrière les requêtes

Les sockets DNS vivent une fraction de seconde : il faut donc échantillonner. Cette commande regarde une fois par seconde pendant une minute et compte les programmes qui avaient un socket DNS ouvert. Les programmes qui interrogent le stub de systemd-resolved apparaissent sous leur propre nom, puisque le stub écoute lui aussi sur le port 53.

Toutes distributions
for i in $(seq 1 60); do sudo ss -unpH '( dport = :53 )'; sleep 1; done \
  | grep -o 'users:(("[^"]*",pid=[0-9]*' | sort | uniq -c | sort -rn

Si systemd-resolve, unbound ou named arrive en tête, les requêtes passent par le résolveur et viennent d'un autre programme : relancez la capture sur la seule interface loopback (-i lo) et comparez les heures. Pour un programme que vous ne connaissez pas, regardez son binaire, son dossier et l'unité qui l'a lancé :

Toutes distributions
sudo ls -l /proc/PID/exe /proc/PID/cwd
sudo tr '\0' ' ' < /proc/PID/cmdline; echo
systemctl status PID

Un client de tunnel est un signe de compromission, pas la compromission elle-même. Cherchez le reste comme dans Comment trouver et supprimer un crypto-mineur sur votre serveur : persistance dans cron, systemd et les clés SSH, binaires système modifiés, et la façon dont l'attaquant est entré.

Bloquer le tunnel

Arrêtez d'abord le programme. Faites ensuite en sorte que le DNS ne puisse sortir que par les résolveurs que vous avez choisis : cette table nftables rejette le DNS vers toute autre adresse, et le DNS over TLS partout. Remplacez 192.0.2.53 par vos résolveurs de l'étape 3 (plusieurs, séparés par des virgules).

Toute distribution avec nftables
sudo nft add table inet dnsguard
sudo nft 'add chain inet dnsguard out { type filter hook output priority 0; policy accept; }'
sudo nft add rule inet dnsguard out ip daddr != { 127.0.0.0/8, 192.0.2.53 } meta l4proto { tcp, udp } th dport 53 drop
sudo nft add rule inet dnsguard out tcp dport 853 drop

Attention: Avec une mauvaise adresse de résolveur, le serveur ne résout plus aucun nom : mises à jour, courrier et la plupart des applications s'arrêtent. Gardez une deuxième session ouverte, testez avec getent hosts debian.org et, en cas d'échec, supprimez la table avec sudo nft delete table inet dnsguard. Les règles couvrent IPv4 ; ajoutez des règles ip6 daddr si le serveur utilise des résolveurs IPv6. La table disparaît au redémarrage.

Un tunnel qui passe par votre propre résolveur fonctionne encore, puisque le résolveur transmet les requêtes. Si vous utilisez unbound, répondez « n'existe pas » pour le domaine du tunnel :

unbound
# In the server: section of unbound.conf
    local-zone: "tunnel.example." always_nxdomain
Toutes distributions
sudo unbound-checkconf && sudo systemctl restart unbound

Avec systemd-resolved seul, il n'y a pas de blocage par domaine : faites pointer le serveur vers un résolveur qui en est capable, ou comptez sur la suppression du programme. /etc/hosts n'aide pas, car il ne peut pas couvrir tous les sous-domaines.

Comment InfraFence détecte le tunneling DNS

Avec **Inspecter le trafic DNS** activé, ce qui est le réglage par défaut, l'agent lit les requêtes et réponses DNS propres au serveur au niveau des paquets : port UDP 53, en lecture seule, avec un filtre qui s'exécute dans le noyau. Chaque événement indique le programme qui a posé la question, y compris quand elle est passée par systemd-resolved.

  • dns_tunneling : 60 sous-domaines distincts ou plus d'un même domaine en 5 minutes, longs en moyenne (24 caractères ou plus), en comptant les sous-domaines de 16 caractères ou plus et les requêtes TXT et NULL. L'événement donne le domaine, le nombre de sous-domaines et leur longueur moyenne.
  • dns_dga : 20 domaines inexistants ou plus d'aspect aléatoire en 5 minutes, la marque d'un malware qui cherche son serveur.
  • dns_threat_resolver (critique) quand une requête part vers un serveur DNS présent sur une liste de menaces, dns_threat_answer quand un nom résout vers une adresse listée, et dns_unlisted_resolver quand un programme utilise un serveur DNS autre que ceux configurés.
  • Les recherches des antivirus et des services de réputation, les vérifications de listes noires du courrier et les grands CDN et clouds sont écartés, et le service DNS propre au serveur n'est pas compté comme un programme suspect.

Voir Menaces sortantes et DNS. Ce qu'il ne fait pas : il signale un tunnel et ne le bloque pas ; les blocages sortants ne s'appliquent qu'aux adresses des listes de menaces (Ports et blocage sortant), le blocage ci-dessus reste donc à votre charge. Il ne voit pas le DNS sur TCP, TLS ou HTTPS, et un tunnel plus lent que les seuils n'est pas signalé.

Questions

Mon pare-feu bloque le trafic sortant. Le tunneling DNS peut-il quand même fonctionner ?
Oui, si le serveur peut interroger un résolveur, ce qui est le cas de presque tous. Le résolveur transmet les requêtes au serveur de noms de l'attaquant à votre place. C'est pourquoi le résolveur doit aussi bloquer le domaine, ou le programme doit disparaître.
DNSSEC ou DNS over HTTPS l'empêchent-ils ?
Non. DNSSEC prouve que les réponses sont authentiques, et les réponses de l'attaquant sont authentiques pour son propre domaine. DNS over HTTPS cache les requêtes au réseau, ce qui rend un tunnel plus difficile à voir, pas impossible à construire.
Quel trafic légitime ressemble à un tunnel ?
Les recherches des antivirus et des services de réputation qui encodent des empreintes de fichiers dans les noms, les vérifications de listes noires d'un serveur de messagerie, et certains noms de CDN, de clouds et de télémétrie. Renseignez-vous sur le domaine avant d'agir.

Essayez gratuitement sur 1 serveur

Starter est gratuit pour un serveur, avec la protection complète et le blocage automatique. Sans carte bancaire.

Installez l'agent en root, avec votre jeton à la place de <TOKEN> :

curl -fsSL https://github.com/infrafence/infrafence-agent/releases/latest/download/install.sh | sudo bash -s -- --token <TOKEN>

Le jeton se crée dans le tableau de bord, dans Serveurs › Ajouter un serveur.