DNS Tunneling Detection
How to detect DNS tunneling without an ML model — query length, entropy, and frequency thresholds, a Suricata rule and a Zeek/SPL analytic, plus egress hardening.
DNS tunneling detection does not need a machine-learning model — it needs three thresholds. Attackers tunnel command-and-control and stolen data through DNS because it is almost always allowed outbound and rarely inspected. They encode data into the subdomain labels of queries to a domain they control. That leaves a clear statistical fingerprint: queries that are too long, too random, and too frequent to one parent domain. This guide ships the Suricata rule and the Zeek/SPL analytic that catch it.
DNS tunneling maps to MITRE ATT&CK T1071.004 — Application Layer Protocol: DNS for C2 and T1048.003 — Exfiltration Over Unencrypted Non-C2 Protocol. It is the network-layer counterpart to the egress problems in SSRF detection and command injection.
What is DNS tunneling?
DNS tunneling encodes arbitrary data into DNS queries and responses to create a covert
channel. The attacker registers a domain and runs an authoritative name server for it;
the compromised host sends queries like
<base32-encoded-data>.tunnel.attacker.com, and the attacker’s name server reads the
encoded data and answers with encoded commands. Because the traffic is “just DNS,” it
sails through egress controls that block everything else.
Tools like iodine and dnscat2 automate it, and real intrusions have used DNS for C2 at the highest level — the SolarWinds SUNBURST backdoor used DNS to stage its activity. The behavior, again, is what you detect: not the tool, but the abnormal query pattern.
What does DNS tunneling look like in traffic?
| Indicator | Normal DNS | Tunneling |
|---|---|---|
| Query name length | Short, human-readable | Long, near the 253-char limit |
| Character distribution | Words, low entropy | High entropy (base32/base64-like) |
| Subdomains per parent | A handful | Hundreds–thousands of unique labels |
| Record types | Mostly A/AAAA | Heavy TXT/NULL/CNAME use |
| Query frequency | Bursty, cacheable | Steady, high-rate to one domain |
No single indicator is conclusive — a long query name can be a CDN, high TXT volume can be email auth. The signal is the combination: long, high-entropy names and a flood of unique subdomains to one parent domain.
How to detect DNS tunneling
Start with a Suricata rule for the simplest indicator — an abnormally long query name (reserve SID 1000005+):
alert dns $HOME_NET any -> any any (
msg:"DARKPWN Abnormally long DNS query name (possible tunneling)";
dns.query; dsize:>150;
classtype:bad-unknown; sid:1000005; rev:1;) Length alone is noisy, so pair it with a behavioral analytic over DNS logs (Zeek or resolver). The high-fidelity signal is many distinct subdomains to one parent domain:
index=dns sourcetype=zeek:dns
| eval parent=replace(query,"^[^.]+\.","")
| eval sub=mvindex(split(query,"."),0)
| bin _time span=1h
| stats dc(sub) AS unique_subs, avg(len(sub)) AS avg_sublen by _time, parent, src_ip
| where unique_subs >= 100 AND avg_sublen >= 20 How much data actually fits in a DNS query?
The arithmetic here is worth doing once, because it explains why the cardinality threshold is the right detection rather than an arbitrary number.
DNS imposes hard structural limits (RFC 1035):
- A single label is at most 63 octets.
- A complete name is at most 255 octets, which is roughly 253 characters in presentation form.
- Payload must be encoded into characters legal in a hostname, so base32 is the common choice at 5 bits per character.
Subtract the attacker’s own domain, the label separators, and protocol overhead, and a query realistically carries on the order of 100 to 150 bytes of actual payload. Responses can carry more — TXT records hold up to 255 bytes per string, and EDNS0 raises the UDP size ceiling — which is why the downstream (command) direction is less constrained than the upstream (exfiltration) one.
Now the consequence. Exfiltrating a 1 MB file at roughly 100 bytes per query needs on the order of 10,000 queries. A 100 MB database dump needs a million. There is no encoding trick that changes the order of magnitude, because the limit is the protocol.
That is the entire basis for the detection: you cannot move meaningful data through DNS without
generating an enormous number of queries to one parent domain. The attacker’s bandwidth
constraint is your signal, and unlike a content signature it cannot be tuned away. It is also why
unique_subs >= 100 in an hour is a defensible threshold rather than a guess — real exfiltration
runs orders of magnitude above it, while normal browsing to a single parent domain runs well
below.
The false positives that look exactly like tunneling
This is the section that determines whether your detection survives its first week, and it is missing from most write-ups.
Endpoint security products tunnel over DNS. Many antivirus and EDR vendors implement cloud reputation lookups by encoding a file hash into a subdomain and querying their own domain. The result is long, high-entropy, high-cardinality queries to a single parent domain from every endpoint you own. That is not similar to DNS tunneling; it is DNS tunneling, performed by a vendor, for a legitimate purpose.
The full noise list:
| Source | Why it looks like tunneling | Handling |
|---|---|---|
| AV/EDR cloud reputation lookups | Hashes encoded in subdomains, from every host | Allowlist the vendor’s parent domain, pin by process where possible |
| Spam filtering and DNSBL | Encodes IPs and hashes into query names | Allowlist, and scope to your mail infrastructure |
| CDN and cloud provider hostnames | Long, random-looking generated names | Allowlist parent domains, not patterns |
| DNS-based load balancing and service discovery | High query volume, generated labels | Allowlist internal parents |
| Telemetry and analytics SDKs | Per-event encoded queries | Investigate once, then allowlist or remove |
Two rules for handling all of it:
- Allowlist by parent domain, never by pattern. “Ignore long high-entropy queries” disables the
detection. “Ignore queries to
vendor.example” removes one known source and leaves everything else visible. - Review the allowlist as an inventory, not as suppression. Every entry is something in your estate with an unmonitored outbound channel. That is an acceptable risk you have chosen, and it should be written down as such — if that vendor is ever compromised, the channel is pre-approved and invisible.
Expect the initial tuning to take a few days and to produce a list of five to fifteen parent domains. If your allowlist is growing past that, you are probably suppressing categories rather than sources.
Low-and-slow DNS C2 defeats a bulk-exfiltration detection
A threshold detection tuned for exfiltration has a specific blind spot, and competent adversaries sit in it deliberately.
The arithmetic above makes DNS a poor channel for moving data and a perfectly adequate one for command and control, which needs very little bandwidth. A beacon checking in every few minutes and receiving short instructions might generate a few hundred queries per day — comfortably below any cardinality threshold set to catch exfiltration, and indistinguishable from an unremarkable application in raw volume.
That variant has a different fingerprint entirely:
- Regularity rather than volume. Queries at a consistent interval, often with jitter, to one parent domain — which is beaconing and detected by timing analysis, not by counting.
- A parent domain with almost no other traffic. Legitimate domains queried by one host at a steady interval and nothing else are rare.
- Persistence over days. Normal DNS relationships start and stop; a beacon does not.
- Unusual record types. Sustained TXT or NULL queries to a domain that never serves web content.
Run both detections. The cardinality analytic catches exfiltration and misses low-rate C2; the beaconing analytic catches low-rate C2 and ignores volume. Deploying only the first and describing it as DNS tunneling coverage overstates what you have — and the C2 case is the one that precedes the incident rather than concluding it.
How do you stop DoH from erasing your visibility?
Every detection in this guide reads resolver logs. Encrypted DNS on port 443 to a third party produces no resolver logs at all, which means an unmanaged DoH client silently removes the entire data source.
The controls, in order of effectiveness:
- Set DNS policy in the browsers themselves. Chrome, Edge, and Firefox all expose enterprise policy to disable DoH or pin it to your own resolver. This is the durable control, because it addresses the software making the choice rather than chasing its traffic.
- Use Firefox’s canary domain. Firefox checks
use-application-dns.netand disables automatic DoH if the resolver returns NXDOMAIN. Configuring your resolver to do so is a one-line change that switches off automatic DoH for an entire browser fleet. - Block known DoH provider endpoints at the firewall, by IP and hostname. Maintaining that list is ongoing work and it catches the common cases.
- Default-deny outbound 443 where you can, so DoH to an arbitrary host is not reachable in the first place. Realistic on servers, rarely on user workstations.
- Block direct outbound port 53 from everything except your resolvers, which is table stakes and still worth verifying rather than assuming.
Be realistic about the ceiling: DoH to an arbitrary host on 443 is genuinely difficult to distinguish from normal HTTPS without inspection. That is an argument for the policy-level controls at the top of the list, and for treating a host that cannot resolve through your resolvers as an anomaly worth investigating in itself.
Log at the client-to-resolver hop. Resolver-to-upstream logs lose the source IP to NAT and caching, and source attribution is what turns an alert into an investigation. If you can only collect one, collect the one that tells you which host asked.
How do you respond to confirmed DNS tunneling?
- Identify the parent domain and check it against the allowlist first. A meaningful share of these alerts are a vendor product nobody documented.
- Establish when the queries started. That timestamp bounds the compromise and is usually close to the initial access.
- Estimate what left, using the arithmetic above. Query count multiplied by realistic payload per query gives a defensible upper bound on data volume — which is exactly the number legal and regulatory conversations require, and one of the few situations where you can bound a breach from network metadata alone.
- Sinkhole rather than block outright. Redirecting the domain to a host you control keeps the channel visible and tells you which other hosts are also beaconing to it. A firewall block ends the visibility along with the traffic.
- Find every host querying that parent domain, not just the one that alerted. This is the fastest scoping question you will get to answer all incident, because the DNS logs already have the complete list.
- Identify the process making the queries. DNS logs give you the host; endpoint telemetry gives you the executable, and that is what identifies the implant.
- Preserve the DNS logs specifically. They are the record of what was exfiltrated and they frequently have shorter retention than everything else in the SIEM.
Step 3 is the unusual one worth remembering. In most exfiltration incidents, quantifying the volume is guesswork. In this one the protocol imposes a hard ceiling per query, so the query count gives you an upper bound you can actually defend in a disclosure discussion.
How to test your DNS tunneling detection
In an isolated lab on infrastructure you own:
- Stand up a tunneling tool (iodine/dnscat2) against a lab domain you control and confirm the Suricata length rule and the cardinality analytic both fire.
- Replay normal DNS (browsing, CDNs, email auth) and confirm the combined rule stays quiet.
- Tune
unique_subsandavg_sublento your environment’s baseline. - Confirm DoH from a client is blocked (or visible) so it can’t bypass the resolver.
How to prevent DNS tunneling
- Default-deny egress so DNS is not the only open path (see SSRF detection).
- Block DoH/DoT to non-approved providers, which otherwise hides queries from your resolver.
- Alert on new authoritative domains receiving steady high-cardinality queries.
Common DNS tunneling detection mistakes
- No resolver/Zeek logging. You cannot detect what you do not record.
- Single-indicator rules. Length or TXT volume alone drowns in false positives.
- Ignoring DoH. Encrypted DNS bypasses an unmonitored resolver entirely.
- Allowing client-to-anywhere DNS. Removes the choke point detection depends on.
- Suppressing by pattern instead of by parent domain. “Ignore long high-entropy queries” disables the detection; “ignore queries to this vendor domain” removes one known source.
- Not recognising AV and EDR reputation lookups. They encode hashes into subdomains from every endpoint you own, and they are the single largest false-positive source.
- Treating the allowlist as suppression rather than inventory. Each entry is a pre-approved, unmonitored outbound channel, and it should be recorded as an accepted risk.
- Tuning only for bulk exfiltration. Low-rate DNS C2 sits below any cardinality threshold and needs timing analysis instead.
- Logging only the resolver’s upstream queries. NAT and caching destroy source attribution, and the host that asked is what turns an alert into an investigation.
- Blocking the domain instead of sinkholing it. A block ends the visibility along with the traffic; a sinkhole shows you every other host that was also beaconing.
- Letting DNS log retention run shorter than everything else. These logs are the record of what was exfiltrated, and they are frequently the first thing trimmed for cost.
What does good DNS visibility actually require?
Every detection in this guide is downstream of one architectural decision, so it is worth stating the target state plainly.
- One resolver path, no exceptions. Every internal client resolves through resolvers you operate. Direct port 53 outbound is blocked at the firewall, and that block is tested rather than assumed — a rule that was correct when written and has since been shadowed by a broader permit is extremely common.
- Query logging at the client-to-resolver hop, with source IP retained. This is the log that answers “which host,” and it is the one worth paying to keep.
- Retention measured against your detection window. If your DNS logs hold seven days and your average dwell time is longer, the investigation you eventually run will have no data for the period that matters. DNS logs are high volume and compress well; keeping metadata longer than full packet capture is usually affordable.
- Enrichment at ingest, not at query time. Domain age, registrar, and threat-intel matches attached when the log arrives make every subsequent search fast. Doing it at query time makes the analytic too slow to run on a schedule, which means it stops being run.
- DoH policy set in the browsers, so the resolver path cannot be bypassed by software making its own choice.
- The allowlist maintained as a document with an owner, because it is a list of approved blind spots rather than a tuning artifact.
Get that architecture right and the detections above are a few hours of work. Get it wrong and no analytic recovers the missing data — which is the general shape of network detection, and unusually stark for DNS because the protocol is both the most permitted and the least inspected thing in most estates.
The corollary is worth stating for anyone prioritising: if you have no resolver logging today, building the analytics is premature. Collect first, verify the collection covers every client population including servers and containers, then detect. A beautifully written cardinality analytic over a log source that covers 60% of your estate reports a clean environment with high confidence and no basis for it.
DNS tunneling detection checklist
- Force internal DNS through controlled resolvers; log every query (or use Zeek).
- Block direct outbound port 53 and unapproved DoH/DoT from clients.
- Deploy the Suricata long-query-name rule.
- Deploy the high-cardinality-subdomain analytic over DNS logs.
- Add Shannon-entropy scoring of subdomains as a third filter.
- Block newly-registered domains and apply DNS threat intel.
- Alert on steady high-rate queries to a single new parent domain.
- Test with a lab tunneling tool and a normal-DNS baseline.
The takeaway
DNS tunneling detection is three thresholds over DNS logs — length, entropy, and distinct-subdomain volume — backed by a Suricata rule and resolver egress control. No ML required; just visibility and a combined signal. Continue with Sysmon configuration, SSRF detection and C2 beaconing detection for the other covert channel, or browse the full Detection Engineering pillar.
Training & tools referenced
Disclosure: Some links below are affiliate links. If you buy through them, darkpwn may earn a commission at no extra cost to you. We only recommend training and tools we actually use in our own lab, and affiliate links never influence editorial coverage.
- TryHackMeAuthorized labs to practice network threat detection and DNS analysisSecurity TrainingStart training
Frequently asked questions
How do you detect DNS tunneling?
You do not need machine learning. Three thresholds catch most tunneling: long query names (data encoded in the subdomain), high entropy or unusual character distribution in those names, and a high volume of distinct subdomains to one parent domain. Combine them over DNS logs from Zeek or your resolver, and add a Suricata rule for oversized query names.
What is DNS tunneling used for?
Attackers use DNS tunneling for command-and-control and data exfiltration because DNS is almost always allowed outbound, even where other egress is blocked. Data is encoded into subdomain labels of queries to an attacker-controlled domain. It maps to MITRE ATT&CK T1071.004 (DNS C2) and T1048.003 (exfiltration over an unencrypted protocol).
Why is DNS a blind spot for exfiltration?
Most networks allow DNS outbound by default and rarely inspect it, so it becomes a covert channel. Internal clients should only talk to your resolvers, and the resolvers' upstream queries should be logged — without that, encoded data leaves in query names with no record.
How do you prevent DNS tunneling?
Force all internal DNS through controlled resolvers, block direct outbound DNS (port 53 and DoH) from clients, log resolver queries, apply threat-intel and newly-registered-domain blocking, and rate-limit or alert on anomalous query patterns. Detection backs up the egress controls for what slips through.