Que fait Analyse des logs DNS pour l’exfiltration ?
Quand l’utiliser
Utilisez cette compétence lorsque :
- Les équipes SOC suspectent une exfiltration de données via du tunneling DNS contournant les pare-feu/proxies
- Le renseignement sur les menaces indique que des adversaires utilisent des canaux C2 basés sur DNS (ex. Cobalt Strike DNS beacon)
- Un UEBA détecte des volumes anormaux de requêtes DNS provenant d’hôtes spécifiques
- L’analyse de logiciels malveillants révèle des capacités de DNS-over-HTTPS (DoH) ou de tunneling DNS
Ne pas utiliser pour le dépannage DNS standard ou la surveillance de disponibilité — cette compétence se concentre sur la détection des abus DNS liés à la sécurité.
Prérequis
- Journalisation des requêtes DNS activée (Windows DNS Server, Bind, Infoblox ou Cisco Umbrella)
- Logs DNS ingérés dans un SIEM (Splunk avec
Stream:DNS, sourcetypedns, ou logs DNS Zeek) - Données DNS passives pour l’analyse historique de résolution de domaines
- Référence du comportement DNS normal (volume de requêtes, distribution des domaines, fréquence des enregistrements TXT)
- Python avec les bibliothèques
mathetcollectionspour le calcul d’entropie
Workflow
Étape 1 : Détection du tunneling DNS via l’analyse de la longueur des sous-domaines
Le tunneling DNS encode des données dans les labels des sous-domaines, créant des requêtes anormalement longues :
index=dns sourcetype="stream:dns" query_type IN ("A", "AAAA", "TXT", "CNAME", "MX")
| eval domain_parts = split(query, ".")
| eval subdomain = mvindex(domain_parts, 0, mvcount(domain_parts)-3)
| eval subdomain_str = mvjoin(subdomain, ".")
| eval subdomain_len = len(subdomain_str)
| eval tld = mvindex(domain_parts, -1)
| eval registered_domain = mvindex(domain_parts, -2).".".tld
| where subdomain_len > 50
| stats count AS queries, dc(query) AS unique_queries,
avg(subdomain_len) AS avg_subdomain_len,
max(subdomain_len) AS max_subdomain_len,
values(src_ip) AS sources
by registered_domain
| where queries > 20
| sort - avg_subdomain_len
| table registered_domain, queries, unique_queries, avg_subdomain_len, max_subdomain_len, sources
Étape 2 : Détection des requêtes de domaines à haute entropie (Détection DGA)
Les algorithmes de génération de domaines produisent des noms de domaine aléatoires :
index=dns sourcetype="stream:dns"
| eval domain_parts = split(query, ".")
| eval sld = mvindex(domain_parts, -2)
| eval sld_len = len(sld)
| eval char_count = sld_len
| eval vowels = len(replace(sld, "[^aeiou]", ""))
| eval consonants = len(replace(sld, "[^bcdfghjklmnpqrstvwxyz]", ""))
| eval digits = len(replace(sld, "[^0-9]", ""))
| eval vowel_ratio = if(char_count > 0, vowels / char_count, 0)
| eval digit_ratio = if(char_count > 0, digits / char_count, 0)
| where sld_len > 12 AND (vowel_ratio < 0.2 OR digit_ratio > 0.3)
| stats count AS queries, dc(query) AS unique_domains, values(src_ip) AS sources
by query
| where unique_domains > 10
| sort - queries
Calcul de l’entropie de Shannon en Python pour les requêtes DNS :
import math
from collections import Counter
def shannon_entropy(text):
"""Calcule l'entropie de Shannon d'une chaîne"""
if not text:
return 0
counter = Counter(text.lower())
length = len(text)
entropy = -sum(
(count / length) * math.log2(count / length)
for count in counter.values()
)
return round(entropy, 4)
# Test avec des exemples
normal_domain = "google" # Faible entropie
dga_domain = "x8kj2m9p4qw7n" # Haute entropie
tunnel_subdomain = "aGVsbG8gd29ybGQ.evil.com" # Données encodées en Base64
print(f"Normal : {shannon_entropy(normal_domain)}") # ~2.25
print(f"DGA : {shannon_entropy(dga_domain)}") # ~3.70
print(f"Tunnel : {shannon_entropy(tunnel_subdomain)}") # ~3.50
# Seuil : entropie > 3.5 pour un sous-domaine = tunneling/DGA probable
Implémentation Splunk du score d’entropie :
index=dns sourcetype="stream:dns"
| eval domain_parts = split(query, ".")
| eval check_string = mvindex(domain_parts, 0)
| eval check_len = len(check_string)
| where check_len > 8
| eval chars = split(check_string, "")
| stats count AS total_chars, dc(chars) AS unique_chars by query, src_ip, check_string, check_len
| eval entropy_estimate = log(unique_chars, 2) * (unique_chars / check_len)
| where entropy_estimate > 3.5
| stats count AS high_entropy_queries, dc(query) AS unique_queries by src_ip
| where high_entropy_queries > 50
| sort - high_entropy_queries
Étape 3 : Détection d’un volume anormal de requêtes DNS
Identifier les hôtes générant un trafic DNS anormal :
index=dns sourcetype="stream:dns" earliest=-24h
| bin _time span=1h
| stats count AS queries, dc(query) AS unique_domains by src_ip, _time
| eventstats avg(queries) AS avg_queries, stdev(queries) AS stdev_queries by src_ip
| eval z_score = (queries - avg_queries) / stdev_queries
| where z_score > 3 OR queries > 5000
| sort - z_score
| table _time, src_ip, queries, unique_domains, avg_queries, z_score
Détection des abus d’enregistrements TXT (méthode de tunneling courante) :
index=dns sourcetype="stream:dns" query_type="TXT"
| stats count AS txt_queries, dc(query) AS unique_txt_domains,
values(query) AS domains by src_ip
| where txt_queries > 100
| eval suspicion = case(
txt_queries > 1000, "CRITIQUE — Tunneling DNS probable",
txt_queries > 500, "HAUTE — Tunneling DNS possible",
txt_queries > 100, "MOYENNE — Volume TXT inhabituel"
)
| sort - txt_queries
| table src_ip, txt_queries, unique_txt_domains, suspicion
Étape 4 : Détection des outils de tunneling DNS connus
Rechercher les signatures des outils de tunneling DNS courants :
index=dns sourcetype="stream:dns"
| eval query_lower = lower(query)
| where (
match(query_lower, "\.dnscat\..*") OR
match(query_lower, "\.dns2tcp\..*") OR
match(query_lower, "\.iodine\..*") OR
match(query_lower, "\.dnscapy\..*") OR
match(query_lower, "\.cobalt.*\.beacon") OR
query_type="NULL" OR
(query_type="TXT" AND len(query) > 100)
)
| stats count by src_ip, query, query_type
| sort - count
Détection du DNS over HTTPS (DoH) contournant le DNS local :
index=proxy OR index=firewall
dest IN ("1.1.1.1", "1.0.0.1", "8.8.8.8", "8.8.4.4",
"9.9.9.9", "149.112.112.112", "208.67.222.222")
dest_port=443
| stats sum(bytes_out) AS total_bytes, count AS connections by src_ip, dest
| where connections > 100 OR total_bytes > 10485760
| eval alert = "Contournement DoH possible — requêtes DNS envoyées via HTTPS à un résolveur public"
| sort - total_bytes
Étape 5 : Corrélation des résultats DNS avec les données d’endpoint
Croiser les DNS suspects avec les données de processus :
index=dns src_ip="192.168.1.105" query="*.evil-tunnel.com" earliest=-24h
| stats count AS dns_queries, earliest(_time) AS first_query, latest(_time) AS last_query
by src_ip, query
| join src_ip [
search index=sysmon EventCode=3 DestinationPort=53 Computer="WORKSTATION-042"
| stats count AS connections, values(Image) AS processes by SourceIp
| rename SourceIp AS src_ip
]
| table src_ip, query, dns_queries, first_query, last_query, processes
Étape 6 : Estimation du volume de données exfiltrées
Estimer le volume de données encodées dans les requêtes DNS :
index=dns src_ip="192.168.1.105" query="*.evil-tunnel.com" earliest=-24h
| eval domain_parts = split(query, ".")
| eval encoded_data = mvindex(domain_parts, 0)
| eval encoded_bytes = len(encoded_data)
| eval decoded_bytes = encoded_bytes * 0.75 -- Facteur de décodage Base64
| stats sum(decoded_bytes) AS total_bytes_estimated, count AS total_queries,
earliest(_time) AS first_seen, latest(_time) AS last_seen
| eval estimated_kb = round(total_bytes_estimated / 1024, 1)
| eval estimated_mb = round(total_bytes_estimated / 1048576, 2)
| eval duration_hours = round((last_seen - first_seen) / 3600, 1)
| eval rate_kbps = round(estimated_kb / (duration_hours * 3600) * 8, 2)
| table total_queries, estimated_mb, duration_hours, rate_kbps, first_seen, last_seen
Concepts clés
| Terme | Définition |
|---|---|
| Tunneling DNS | Technique encodant des données dans les requêtes/réponses DNS pour exfiltrer des données ou établir des canaux C2 via DNS |
| DGA | Algorithme de génération de domaines — technique de logiciel malveillant générant des noms de domaine pseudo-aléatoires pour la résilience du C2 |
| Entropie de Shannon | Mesure mathématique du caractère aléatoire d’une chaîne — une entropie élevée (>3,5) dans les noms de domaine indique une DGA ou un tunneling |
| Abus d’enregistrement TXT | Utilisation des enregistrements TXT DNS (conçus pour des données textuelles) comme canal à large bande pour le tunneling de données |
| DNS over HTTPS (DoH) | Requêtes DNS chiffrées via HTTPS (port 443), contournant la surveillance DNS traditionnelle |
| DNS passif | Enregistrement historique des résolutions DNS montrant à quelles IP un domaine a résolu au fil du temps |
Outils et systèmes
- Splunk Stream : Add-on de capture de trafic réseau fournissant des données de requêtes DNS analysées pour une analyse dans le SIEM
- Zeek (Bro) : Moniteur de sécurité réseau générant des journaux détaillés de transactions DNS pour analyse
- Cisco Umbrella (OpenDNS) : Plateforme de sécurité DNS cloud bloquant les domaines malveillants et journalisant les données de requêtes
- Infoblox DNS Firewall : Sécurité au niveau DNS fournissant un blocage basé sur RPZ et une journalisation détaillée des requêtes
- Farsight DNSDB : Base de données DNS passive pour les recherches de résolution historique de domaines et la cartographie de l’infrastructure
Scénarios courants
- Cobalt Strike DNS Beacon : Détecter des requêtes TXT périodiques avec des charges utiles encodées vers un domaine C2
- Exfiltration de données : Volumes importants de requêtes de sous-domaines uniques encodant des données volées en Base64/hexadécimal
- Logiciel malveillant DGA : Détecter des requêtes DNS vers des domaines générés algorithmiquement (haute entropie, pas de contenu web)
- Contournement DNS-over-HTTPS : Employé utilisant DoH pour contourner le filtrage et la surveillance DNS de l’entreprise
- Exfiltration lente : Tunneling DNS à faible volume restant sous les seuils d’alerte (nécessite une comparaison avec la ligne de base)
Format de sortie
ANALYSE D’EXFILTRATION DNS — WORKSTATION-042
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Période : 2024-03-14 au 2024-03-15
Source : 192.168.1.105 (WORKSTATION-042, Service Finances)
Résultats :
[CRITIQUE] Tunneling DNS détecté vers evil-tunnel[.]com
Volume de requêtes : 12 847 requêtes en 18 heures
Longueur moyenne des sous-domaines : 63 caractères (normal : <20)
Entropie moyenne : 3,82 (seuil : 3,5)
Types de requêtes : TXT (89%), A (11%)
Données estimées : ~4,7 Mo exfiltrées via DNS
Débit : 0,58 kbps (motif d’exfiltration lente)
[HAUTE] Domaines de type DGA résolus
Domaines DGA uniques : 247 domaines résolus
Modèle : 15 caractères alphanumériques aléatoires .xyz TLD
Plage d’entropie : 3,6 - 4,1
Attribution de processus :
Processus : svchost_update.exe (usurpation — svchost légitime non concerné)
PID : 4892
Parent : explorer.exe
Empreinte : SHA256 : a1b2c3d4... (VT : 34/72 malveillant — beacon Cobalt Strike)
Confinement :
[FAIT] Hôte isolé via l’EDR
[FAIT] Domaine evil-tunnel[.]com ajouté au DNS sinkhole
[FAIT] Incident IR-2024-0448 créé