A DNS tunnel encodes arbitrary traffic into hostnames and record data so that it travels through the recursive resolver path to an authoritative server the operator controls. Where our DNS C2 pack focuses on beaconing messages, this pack focuses on the transport: volume, shape and statistics that betray a sustained bidirectional channel.
Signals at a Glance
| Signal | Where | ATT&CK |
|---|---|---|
| Very high count of unique subdomains under one parent zone | DNS logs, Zeek dns.log | T1572 |
| Long query names near the 253-character limit with encoded labels | DNS logs | T1572 |
| High-entropy labels (base32, base64 or hex-looking) | DNS logs | T1048 |
| Unusual record-type mix: heavy TXT, NULL, CNAME or MX from a workstation | DNS logs | T1071.004 |
| Sustained query rate from one client to one zone for hours | DNS logs, resolver metrics | T1572 |
Statistical Detection
Legitimate zones have bounded, repeated names. A tunnel generates a new name for nearly every packet, so the number of distinct subdomains per client and zone grows without bound. Start with cardinality and length over a rolling window:
# Splunk (Zeek dns.log) - subdomain cardinality and length by client and zone
index=zeek sourcetype=bro_dns
| eval labels=split(query,".")
| eval zone=mvindex(labels,-2).".".mvindex(labels,-1)
| eval sub_len=len(query)-len(zone)
| bin _time span=1h
| stats count AS queries, dc(query) AS unique_names, avg(sub_len) AS avg_sub_len,
dc(qtype_name) AS qtypes BY _time, id.orig_h, zone
| where unique_names > 500 AND avg_sub_len > 30
| sort - unique_names
# tune/validate: exclude CDNs, anti-virus lookups and DNS-based blocklists;
# for country-code second-level zones use a public-suffix lookup instead.
Record Types and Throughput
Tunnel tools pick the record type that carries the most data per response. TXT, NULL and CNAME answers are common; ordinary workstation traffic is dominated by A and AAAA. Compare the record-type mix and response sizes against what you see across the fleet:
// Microsoft Sentinel (KQL) - non-A/AAAA heavy clients
DnsEvents
| where TimeGenerated > ago(1d)
| summarize total=count(),
txt=countif(QueryType in ("TXT","NULL","CNAME","MX")),
uniq=dcount(Name) by ClientIP, bin(TimeGenerated, 1h)
| extend txt_ratio = todouble(txt) / total
| where total > 300 and txt_ratio > 0.5 and uniq > 200
| order by uniq desc
// tune/validate: mail servers and security agents produce legitimate MX/TXT
// bursts; scope to endpoints and user-subnets.
- Correlate with the process that issued the queries when endpoint DNS telemetry is available; a non-resolver process generating the volume is conclusive.
- Block direct outbound port 53 from endpoints so that all queries pass through monitored resolvers.
- Add a sinkhole or response-policy entry for confirmed tunnel zones and review who queried them in the past 30 days.
- Enable DNS query logging on the recursive resolvers; without it none of these analytics can run.
Use Zeek dns.log, Splunk ESCU DNS exfiltration and tunneling analytics, Elastic DNS tunneling rules, SigmaHQ DNS rules and MITRE CAR analytics for DNS. Always baseline your own environment — thresholds are zone- and tenant-specific and need tuning against your resolver telemetry.
Build Detections That Fire
Mjolnir Security helps blue teams turn threat intelligence into high-fidelity detections across endpoint, network, identity and cloud telemetry.
- Detection Engineering We build and tune detections mapped to ATT&CK from your own telemetry.
- Threat Hunting as a Service Hypothesis-driven hunts for the behaviours on this page.
- 24/7 Incident Response Containment when a detection fires. Call +1 833 403 5875.
Stay ahead of emerging threats. Get notified when we publish new intelligence reports and advisories.
Subscribe to Alerts