Detection rules › Elastic

Potential DNS Exfiltration via Excessive Chunked Queries

Status
production
Severity
medium
Time window
9m
Group by
DATE_TRUNC(5 minutes, @timestamp), Esql.base_domain, data_stream.namespace, host.id, host.name, process.name, user.id, user.name
Author
Elastic
Source
github.com/elastic/detection-rules

Identifies potential DNS exfiltration on Windows hosts by detecting a high volume of DNS queries whose subdomain labels follow a chunked encoding pattern (index-payload.base_domain). Attackers split stolen data across many DNS queries to evade volume-based detection; this rule aggregates queries per process, base domain, and five-minute window and flags sessions with many distinct chunk indices and sufficiently long encoded payloads.

MITRE ATT&CK coverage

Telemetry coverage

ProviderRecord / event type
SysmonEvent ID 22: DNSEvent (DNS query)

Rule body

[metadata]
creation_date = "2026/07/03"
integration = ["endpoint", "windows", "crowdstrike"]
maturity = "production"
updated_date = "2026/07/03"

[rule]
author = ["Elastic"]
description = """
Identifies potential DNS exfiltration on Windows hosts by detecting a high volume of DNS queries whose subdomain labels
follow a chunked encoding pattern (index-payload.base_domain). Attackers split stolen data across many DNS queries to
evade volume-based detection; this rule aggregates queries per process, base domain, and five-minute window and flags
sessions with many distinct chunk indices and sufficiently long encoded payloads.
"""
from = "now-9m"
interval = "5m"
language = "esql"
license = "Elastic License v2"
name = "Potential DNS Exfiltration via Excessive Chunked Queries"
note = """## Triage and analysis

### Investigating Potential DNS Exfiltration via Excessive Chunked Queries

DNS tunneling and exfiltration often encode data in subdomain labels using a chunk index prefix (for example,
`42-<base64-or-hex-payload>.attacker.example`). A large number of distinct chunk indices to the same base domain from
one process within a short window strongly suggests staged data transfer rather than normal resolution behavior.

### Possible investigation steps

- Review **Esql.base_domain**, **Esql.unique_chunks**, and **Esql.max_index** on the alert to gauge exfil volume and
  whether chunk indices form a contiguous or near-contiguous sequence.
- Identify **process.name** and **process.executable** (from related network events on the same host) and inspect the
  process tree for scripting runtimes, LOLBins, or unsigned binaries.
- Pivot on **host.id** for other DNS, network, or exfiltration alerts in the past 48 hours.
- Inspect sample **dns.question.name** values for the session to confirm encoded payload subdomains and estimate data
  volume (**Esql.avg_payload_len** × **Esql.unique_chunks**).
- Check whether the base domain is newly observed, lacks business justification, or resolves to infrastructure outside
  approved DNS allowlists.

### False positive analysis

- Legitimate software that encodes telemetry or session tokens in DNS labels is rare; validate against known vendor
  behavior before closing.
- Security scanners or research tools that generate synthetic chunked DNS labels may match; confirm process identity and
  organizational ownership.

### Response and remediation

- If confirmed malicious: isolate the host, block the **Esql.base_domain** at DNS and egress controls, and preserve
  DNS/network logs for scoping.
- Hunt for the same **Esql.base_domain** and process hash across other hosts and users.
- Reset credentials and review data accessible to the involved user or process if exfiltration is confirmed.
"""

setup = """## Setup

This rule is designed for data generated by [Elastic Defend](https://www.elastic.co/security/endpoint-security), which provides native endpoint detection and response, along with event enrichments designed to work with our detection rules.

Setup instructions: https://ela.st/install-elastic-defend

### Additional data sources

This rule also supports the following third-party data sources. For setup instructions, refer to the links below:

- [CrowdStrike](https://ela.st/crowdstrike-integration)
- [Sysmon Event ID 22 - DNS Query](https://ela.st/sysmon-event-22-setup)
"""

references = [
    "https://attack.mitre.org/techniques/T1048/003/",
    "https://attack.mitre.org/techniques/T1572/",
    "https://unit42.paloaltonetworks.com/dns-tunneling-how-dns-can-be-abused-by-malicious-actors/",
]
risk_score = 47
rule_id = "882a39ad-a404-45e3-b5d4-bc11d2b09818"
severity = "medium"
tags = [
    "Domain: Endpoint",
    "OS: Windows",
    "Use Case: Threat Detection",
    "Tactic: Exfiltration",
    "Resources: Investigation Guide",
    "Data Source: Elastic Defend",
    "Data Source: Crowdstrike",
    "Data Source: Sysmon",
]
timestamp_override = "event.ingested"
type = "esql"

query = '''
FROM logs-crowdstrike.fdr*, logs-endpoint.events.network-*, logs-windows.sysmon_operational-*
| WHERE host.os.type == "windows"
    AND event.category == "network"
    AND event.action IN ("lookup_requested", "DNSEvent (DNS query)", "DnsRequest")
    AND process.name != "svchost.exe"
    AND dns.question.name RLIKE """[0-9]{1,5}-[A-Za-z0-9+/=]{15,63}\..+"""
| GROK dns.question.name "%{INT:chunk_index}-%{DATA:chunk_payload}\\.%{GREEDYDATA:Esql.base_domain}"
| WHERE chunk_index IS NOT NULL
| EVAL payload_len = LENGTH(chunk_payload)
| STATS
    Esql.occurrences = COUNT(*),
    Esql.unique_chunks = COUNT_DISTINCT(chunk_index),
    Esql.max_index = MAX(TO_INTEGER(chunk_index)),
    Esql.avg_payload_len = AVG(payload_len)
  BY process.name, Esql.base_domain, user.id, user.name, host.id, host.name, data_stream.namespace, DATE_TRUNC(5 minutes, @timestamp)
| WHERE Esql.occurrences >= 30
    AND Esql.unique_chunks >= 30
    AND Esql.avg_payload_len >= 20
| SORT Esql.unique_chunks DESC
| LIMIT 20
| KEEP host.id, host.name, process.name, user.id, user.name, data_stream.namespace, Esql.*
'''

[rule.investigation_fields]
field_names = [
    "@timestamp",
    "host.id",
    "host.name",
    "user.id",
    "user.name",
    "process.name",
    "dns.question.name",
]

[[rule.threat]]
framework = "MITRE ATT&CK"

[[rule.threat.technique]]
id = "T1048"
name = "Exfiltration Over Alternative Protocol"
reference = "https://attack.mitre.org/techniques/T1048/"

[[rule.threat.technique.subtechnique]]
id = "T1048.003"
name = "Exfiltration Over Unencrypted Non-C2 Protocol"
reference = "https://attack.mitre.org/techniques/T1048/003/"

[rule.threat.tactic]
id = "TA0010"
name = "Exfiltration"
reference = "https://attack.mitre.org/tactics/TA0010/"

[[rule.threat]]
framework = "MITRE ATT&CK"

[[rule.threat.technique]]
id = "T1572"
name = "Protocol Tunneling"
reference = "https://attack.mitre.org/techniques/T1572/"

[rule.threat.tactic]
id = "TA0011"
name = "Command and Control"
reference = "https://attack.mitre.org/tactics/TA0011/"

Stages and Predicates

Stage 1: from

FROM logs-crowdstrike.fdr*, logs-endpoint.events.network-*, logs-windows.sysmon_operational-*

Stage 2: where

| WHERE host.os.type == "windows"
    AND event.category == "network"
    AND event.action IN ("lookup_requested", "DNSEvent (DNS query)", "DnsRequest")
    AND process.name != "svchost.exe"
    AND dns.question.name RLIKE """[0-9]{1,5}-[A-Za-z0-9+/=]{15,63}\..+"""

Stage 3: grok

| GROK dns.question.name "%{INT:chunk_index}-%{DATA:chunk_payload}\\.%{GREEDYDATA:Esql.base_domain}"

Stage 4: where

| WHERE chunk_index IS NOT NULL

Stage 5: eval

| EVAL payload_len = LENGTH(chunk_payload)

Stage 6: stats

| STATS
    Esql.occurrences = COUNT(*),
    Esql.unique_chunks = COUNT_DISTINCT(chunk_index),
    Esql.max_index = MAX(TO_INTEGER(chunk_index)),
    Esql.avg_payload_len = AVG(payload_len)
  BY process.name, Esql.base_domain, user.id, user.name, host.id, host.name, data_stream.namespace, DATE_TRUNC(5 minutes, @timestamp)

Stage 7: where

| WHERE Esql.occurrences >= 30
    AND Esql.unique_chunks >= 30
    AND Esql.avg_payload_len >= 20

Stage 8: sort

| SORT Esql.unique_chunks DESC

Stage 9: limit

| LIMIT 20

Stage 10: keep

| KEEP host.id, host.name, process.name, user.id, user.name, data_stream.namespace, Esql.*

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
host.idKEEP host.id
host.nameKEEP host.name
process.nameKEEP process.name
user.idKEEP user.id
user.nameKEEP user.name
data_stream.namespaceKEEP data_stream.namespace
Esql.*KEEP Esql.*