Potential DNS Tunneling via NULL or Long DNS Queries

Identifies DNS query patterns associated with tunneling, including rare NULL queries, repeated TXT queries with unusually long names, and clients generating many unique, long subdomains under one registered domain within a five-minute window.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2026/08/20"
  3integration = ["network_traffic", "fortinet_fortigate", "zeek"]
  4maturity = "production"
  5updated_date = "2026/09/21"
  6
  7[rule]
  8author = ["Elastic"]
  9description = """
 10Identifies DNS query patterns associated with tunneling, including rare NULL queries, repeated TXT queries with
 11unusually long names, and clients generating many unique, long subdomains under one registered domain within a
 12five-minute window.
 13"""
 14false_positives = [
 15    """
 16    CDN, cloud load-balancer, software-update, and telemetry hostnames can be long and change often. Recursive
 17    resolvers, forwarders, NAT gateways, and localhost DNS listeners can also combine queries from many endpoints under
 18    one client address. DNS research, testing, and security tools can generate NULL or unusually long TXT queries.
 19    Validate the apex domain and whether the source is an endpoint before treating the activity as tunneling.
 20    """,
 21]
 22from = "now-9m"
 23language = "esql"
 24license = "Elastic License v2"
 25name = "Potential DNS Tunneling via NULL or Long DNS Queries"
 26note = """## Triage and analysis
 27
 28### Investigating Potential DNS Tunneling via NULL or Long DNS Queries
 29
 30DNS tunneling encodes data in query labels and often produces many unique, unusually long subdomains under a single apex
 31domain. Some tunneling tools also use rare NULL queries or long TXT query names. This rule aggregates network DNS
 32telemetry for those behavioral patterns without relying on threat intelligence feeds or machine learning jobs.
 33
 34Compare overlapping apex domains against the machine learning **DNS Tunneling** rule when that job is enabled.
 35
 36### Possible investigation steps
 37
 38- Review `Esql.dns_registered_domain`, `Esql.dns_question_type_values`, `Esql.total_qualifying_queries`,
 39  `Esql.count_distinct_names`, `Esql.unique_name_ratio`, `Esql.max_question_name_length`,
 40  `Esql.max_subdomain_length`, and sample values in `Esql.sample_names`.
 41- NULL alerts require at least three queries and two distinct names. Long-TXT alerts require at least five distinct
 42  names longer than 100 characters. The general long-subdomain branch retains the original threshold of 25 queries and
 43  15 distinct names across all query types.
 44- `Esql.count_queries`, `Esql.count_distinct_names`, and `Esql.unique_name_ratio` describe only long-subdomain
 45  activity. For alerts triggered only by NULL or long-TXT queries, both counts may be zero and the ratio will be null.
 46- TXT, NULL, CNAME, or MX bursts increase confidence; A/AAAA-only activity can still be tunneling and should not be
 47  dismissed on type alone.
 48- Use `Esql.first_seen`, `Esql.last_seen`, `Esql.dataset_values`, and `Esql.observer_name_values` to establish the event
 49  span and identify the integrations and sensors that contributed to the alert.
 50- Confirm whether `Esql.client_ip` is a workstation, server, recursive resolver, forwarder, NAT address, or localhost
 51  DNS service. Resolver and localhost sources merge many clients and are a common false-positive pattern.
 52- Review `Esql.destination_ip_values` to identify the resolver or authoritative destination observed by the sensor.
 53- Pivot on the same client and apex domain in raw DNS events and look for follow-on process, file, or additional C2
 54  activity.
 55
 56### False positive analysis
 57
 58- CDN, cloud load-balancer, certificate, and software-update services often create long hostnames. Confirm the apex
 59  domain reputation and whether the requesting host role normally uses that provider.
 60- Security or network appliances performing DNS-based reachability or reputation checks can resemble tunneling. Exclude
 61  confirmed appliance addresses after validation.
 62- Do not create a global resolver exception until the originating endpoint is known; a shared `Esql.client_ip` can hide
 63  a single infected host behind legitimate bulk lookups.
 64
 65### Response and remediation
 66
 67- Block the apex domain or forwarding from the affected host at recursive resolvers if malicious activity is confirmed.
 68- Isolate the source host and inspect for tunneling tools or malware initiating the queries.
 69- Add temporary blocks for the apex domain while scoping additional hosts querying the same name.
 70"""
 71references = [
 72    "https://code.kryo.se/iodine/",
 73    "https://unit42.paloaltonetworks.com/dns-tunneling-how-dns-can-be-abused-by-malicious-actors/",
 74]
 75risk_score = 47
 76rule_id = "89ed957d-609b-4b00-b8c6-a5cbd187632c"
 77setup = """## Setup
 78
 79This rule requires DNS transaction events from one of the following sources:
 80
 81- Elastic Network Packet Capture (`network_traffic.dns`) in `logs-network_traffic.dns-*`
 82- Fortinet FortiGate DNS logs (`fortinet_fortigate.log`) in `logs-fortinet_fortigate.log-*`
 83- Elastic Zeek (`zeek.dns`) in `logs-zeek.dns-*`
 84- Legacy Packetbeat DNS events in `packetbeat-*` with `event.dataset` set to `dns`
 85
 86Place the sensor where it observes endpoint-to-resolver DNS traffic. If the sensor is upstream of a recursive resolver,
 87or if the captured client is a localhost listener such as `127.0.0.1`, `Esql.client_ip` may identify shared DNS
 88infrastructure instead of the originating endpoint.
 89
 90DNS-over-HTTPS (DoH), DNS-over-TLS (DoT), and other encrypted DNS traffic are not visible to packet capture unless the
 91sensor receives decrypted DNS telemetry or equivalent resolver logs mapped to ECS.
 92"""
 93severity = "medium"
 94tags = [
 95    "Domain: Network",
 96    "Use Case: Threat Detection",
 97    "Use Case: Network Security Monitoring",
 98    "Rule Type: ES|QL",
 99    "Tactic: Command and Control",
100    "Tactic: Exfiltration",
101    "Data Source: Network Packet Capture",
102    "Data Source: Fortinet",
103    "Data Source: Network Traffic",
104    "Data Source: Zeek",
105    "Resources: Investigation Guide",
106]
107timestamp_override = "event.ingested"
108type = "esql"
109
110query = '''
111from logs-network_traffic.dns-*, logs-fortinet_fortigate.log-*, logs-zeek.dns-*, packetbeat-*
112| where
113    (
114      data_stream.dataset in ("network_traffic.dns", "fortinet_fortigate.log", "zeek.dns")
115      or event.dataset == "dns"
116    )
117    and dns.question.name is not null
118    and dns.question.registered_domain is not null
119| eval
120    Esql.client_ip = COALESCE(client.ip, source.ip),
121    Esql.dataset = COALESCE(data_stream.dataset, event.dataset),
122    Esql.dns_question_name = TO_LOWER(dns.question.name),
123    Esql.dns_registered_domain = TO_LOWER(dns.question.registered_domain),
124    Esql.dns_question_type = TO_LOWER(dns.question.type),
125    Esql.question_name_length = LENGTH(Esql.dns_question_name),
126    Esql.subdomain_length = LENGTH(Esql.dns_question_name) - LENGTH(Esql.dns_registered_domain) - 1
127| where
128    Esql.client_ip is not null
129    and (Esql.dns_question_type is null or Esql.dns_question_type != "ptr")
130    and not ENDS_WITH(Esql.dns_question_name, ".arpa")
131    and (
132      Esql.dns_question_type == "null"
133      or Esql.subdomain_length >= 50
134      or (Esql.dns_question_type == "txt" and Esql.question_name_length > 100)
135    )
136| eval
137    Esql.long_subdomain_query_name = CASE(
138      Esql.subdomain_length >= 50,
139      Esql.dns_question_name,
140      null
141    ),
142    Esql.null_query_name = CASE(
143      Esql.dns_question_type == "null",
144      Esql.dns_question_name,
145      null
146    ),
147    Esql.long_txt_query_name = CASE(
148      Esql.dns_question_type == "txt" and Esql.question_name_length > 100,
149      Esql.dns_question_name,
150      null
151    )
152| eval Esql.time_window = DATE_TRUNC(5 minutes, @timestamp)
153| stats
154    Esql.total_qualifying_queries = COUNT(*),
155    Esql.count_queries = COUNT(Esql.long_subdomain_query_name),
156    Esql.count_distinct_names = COUNT_DISTINCT(Esql.long_subdomain_query_name),
157    Esql.null_query_count = COUNT(Esql.null_query_name),
158    Esql.null_distinct_names = COUNT_DISTINCT(Esql.null_query_name),
159    Esql.long_txt_query_count = COUNT(Esql.long_txt_query_name),
160    Esql.long_txt_distinct_names = COUNT_DISTINCT(Esql.long_txt_query_name),
161    Esql.max_question_name_length = MAX(Esql.question_name_length),
162    Esql.avg_question_name_length = AVG(Esql.question_name_length),
163    Esql.max_subdomain_length = MAX(Esql.subdomain_length),
164    Esql.avg_subdomain_length = AVG(Esql.subdomain_length),
165    Esql.dns_question_type_values = MV_SLICE(VALUES(Esql.dns_question_type), 0, 9),
166    Esql.destination_ip_values = MV_SLICE(VALUES(destination.ip), 0, 4),
167    Esql.sample_names = MV_SLICE(VALUES(Esql.dns_question_name), 0, 4),
168    Esql.dataset_values = MV_SLICE(VALUES(Esql.dataset), 0, 9),
169    Esql.observer_name_values = MV_SLICE(VALUES(observer.name), 0, 19),
170    Esql.first_seen = MIN(@timestamp),
171    Esql.last_seen = MAX(@timestamp)
172  by Esql.time_window, Esql.client_ip, Esql.dns_registered_domain
173| eval Esql.unique_name_ratio = CASE(
174    Esql.count_queries > 0,
175    TO_DOUBLE(Esql.count_distinct_names) / Esql.count_queries,
176    null
177  )
178| where
179    (
180      Esql.count_queries >= 25
181      and Esql.count_distinct_names >= 15
182    )
183    or (
184      Esql.null_query_count >= 3
185      and Esql.null_distinct_names >= 2
186    )
187    or (
188      Esql.long_txt_query_count >= 5
189      and Esql.long_txt_distinct_names >= 5
190    )
191| keep Esql.*
192'''
193
194
195[[rule.threat]]
196framework = "MITRE ATT&CK"
197[[rule.threat.technique]]
198id = "T1071"
199name = "Application Layer Protocol"
200reference = "https://attack.mitre.org/techniques/T1071/"
201[[rule.threat.technique.subtechnique]]
202id = "T1071.004"
203name = "DNS"
204reference = "https://attack.mitre.org/techniques/T1071/004/"
205
206
207[[rule.threat.technique]]
208id = "T1572"
209name = "Protocol Tunneling"
210reference = "https://attack.mitre.org/techniques/T1572/"
211
212
213[rule.threat.tactic]
214id = "TA0011"
215name = "Command and Control"
216reference = "https://attack.mitre.org/tactics/TA0011/"
217[[rule.threat]]
218framework = "MITRE ATT&CK"
219[[rule.threat.technique]]
220id = "T1048"
221name = "Exfiltration Over Alternative Protocol"
222reference = "https://attack.mitre.org/techniques/T1048/"
223[[rule.threat.technique.subtechnique]]
224id = "T1048.003"
225name = "Exfiltration Over Unencrypted Non-C2 Protocol"
226reference = "https://attack.mitre.org/techniques/T1048/003/"
227
228
229
230[rule.threat.tactic]
231id = "TA0010"
232name = "Exfiltration"
233reference = "https://attack.mitre.org/tactics/TA0010/"
234
235[rule.investigation_fields]
236field_names = [
237    "Esql.client_ip",
238    "Esql.dns_registered_domain",
239    "Esql.time_window",
240    "Esql.total_qualifying_queries",
241    "Esql.count_queries",
242    "Esql.count_distinct_names",
243    "Esql.unique_name_ratio",
244    "Esql.null_query_count",
245    "Esql.null_distinct_names",
246    "Esql.long_txt_query_count",
247    "Esql.long_txt_distinct_names",
248    "Esql.dns_question_type_values",
249    "Esql.max_question_name_length",
250    "Esql.avg_question_name_length",
251    "Esql.max_subdomain_length",
252    "Esql.avg_subdomain_length",
253    "Esql.destination_ip_values",
254    "Esql.sample_names",
255    "Esql.dataset_values",
256    "Esql.observer_name_values",
257    "Esql.first_seen",
258    "Esql.last_seen",
259]

Triage and analysis

Investigating Potential DNS Tunneling via NULL or Long DNS Queries

DNS tunneling encodes data in query labels and often produces many unique, unusually long subdomains under a single apex domain. Some tunneling tools also use rare NULL queries or long TXT query names. This rule aggregates network DNS telemetry for those behavioral patterns without relying on threat intelligence feeds or machine learning jobs.

Compare overlapping apex domains against the machine learning DNS Tunneling rule when that job is enabled.

Possible investigation steps

  • Review Esql.dns_registered_domain, Esql.dns_question_type_values, Esql.total_qualifying_queries, Esql.count_distinct_names, Esql.unique_name_ratio, Esql.max_question_name_length, Esql.max_subdomain_length, and sample values in Esql.sample_names.
  • NULL alerts require at least three queries and two distinct names. Long-TXT alerts require at least five distinct names longer than 100 characters. The general long-subdomain branch retains the original threshold of 25 queries and 15 distinct names across all query types.
  • Esql.count_queries, Esql.count_distinct_names, and Esql.unique_name_ratio describe only long-subdomain activity. For alerts triggered only by NULL or long-TXT queries, both counts may be zero and the ratio will be null.
  • TXT, NULL, CNAME, or MX bursts increase confidence; A/AAAA-only activity can still be tunneling and should not be dismissed on type alone.
  • Use Esql.first_seen, Esql.last_seen, Esql.dataset_values, and Esql.observer_name_values to establish the event span and identify the integrations and sensors that contributed to the alert.
  • Confirm whether Esql.client_ip is a workstation, server, recursive resolver, forwarder, NAT address, or localhost DNS service. Resolver and localhost sources merge many clients and are a common false-positive pattern.
  • Review Esql.destination_ip_values to identify the resolver or authoritative destination observed by the sensor.
  • Pivot on the same client and apex domain in raw DNS events and look for follow-on process, file, or additional C2 activity.

False positive analysis

  • CDN, cloud load-balancer, certificate, and software-update services often create long hostnames. Confirm the apex domain reputation and whether the requesting host role normally uses that provider.
  • Security or network appliances performing DNS-based reachability or reputation checks can resemble tunneling. Exclude confirmed appliance addresses after validation.
  • Do not create a global resolver exception until the originating endpoint is known; a shared Esql.client_ip can hide a single infected host behind legitimate bulk lookups.

Response and remediation

  • Block the apex domain or forwarding from the affected host at recursive resolvers if malicious activity is confirmed.
  • Isolate the source host and inspect for tunneling tools or malware initiating the queries.
  • Add temporary blocks for the apex domain while scoping additional hosts querying the same name.

References

Related rules

to-top