Detection rules › Splunk
DNS Query Length With High Standard Deviation
The following analytic identifies DNS queries with unusually large lengths by computing the standard deviation of query lengths and filtering those exceeding two times the standard deviation. It leverages DNS query data from the Network_Resolution data model, focusing on the length of the domain names being resolved. This activity is significant as unusually long DNS queries can indicate data exfiltration or command-and-control communication attempts. If confirmed malicious, this activity could allow attackers to stealthily transfer data or maintain persistent communication channels within the network.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Exfiltration | T1048.003 Exfiltration Over Alternative Protocol: Exfiltration Over Unencrypted Non-C2 Protocol |
Event coverage
| Provider | Event | Title |
|---|---|---|
| Sysmon | Event ID 22 | DNSEvent (DNS query) |
Rule body splunk
name: DNS Query Length With High Standard Deviation
id: 1a67f15a-f4ff-4170-84e9-08cf6f75d6f5
version: 16
creation_date: '2019-10-16'
modification_date: '2026-05-13'
author: Bhavin Patel, Splunk
status: production
type: Anomaly
description: The following analytic identifies DNS queries with unusually large lengths by computing the standard deviation of query lengths and filtering those exceeding two times the standard deviation. It leverages DNS query data from the Network_Resolution data model, focusing on the length of the domain names being resolved. This activity is significant as unusually long DNS queries can indicate data exfiltration or command-and-control communication attempts. If confirmed malicious, this activity could allow attackers to stealthily transfer data or maintain persistent communication channels within the network.
data_source:
- Sysmon EventID 22
search: |-
| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time) as lastTime FROM datamodel=Network_Resolution
WHERE NOT DNS.record_type IN ("Pointer","PTR","SOA", "SRV") DNS.query != *.
BY DNS.answer DNS.answer_count DNS.query
DNS.query_count DNS.reply_code_id DNS.src
DNS.vendor_product DNS.dest DNS.record_type
| `drop_dm_object_name("DNS")`
| `security_content_ctime(firstTime)`
| `security_content_ctime(lastTime)`
| eval tlds=split(query,".")
| eval tld=mvindex(tlds,-1)
| eval tld_len=len(tld)
| search tld_len<=20
| eval query_length = len(query)
| table firstTime lastTime src dest query query_length record_type count record_type
| eventstats stdev(query_length) AS stdev avg(query_length) AS avg p50(query_length) AS p50
| where query_length>(avg+stdev*2)
| eval z_score=(query_length-avg)/stdev
| stats count values(query) as query values(dest) as dest avg(query_length) as avg_query_length values(record_type) as record_type min(firstTime) as firstTime latest(lastTime) as lastTime
BY src
| `dns_query_length_with_high_standard_deviation_filter`
how_to_implement: To successfully implement this search, you will need to ensure that DNS data is populating the Network_Resolution data model.
known_false_positives: It's possible there can be long domain names that are legitimate.
references: []
drilldown_searches:
- name: View the detection results for - "$src$"
search: '%original_detection_search% | search src = "$src$"'
earliest_offset: $info_min_time$
latest_offset: $info_max_time$
- name: View risk events for the last 7 days for - "$src$"
search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$host$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`'
earliest_offset: 7d
latest_offset: "0"
intermediate_findings:
entities:
- field: src
type: system
score: 20
message: Potentially suspicious DNS query [$query$] with high standard deviation from src - [$src$]
threat_objects:
- field: query
type: url
analytic_story:
- Hidden Cobra Malware
- Suspicious DNS Traffic
- Command And Control
asset_type: Endpoint
mitre_attack_id:
- T1048.003
product:
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
category: network
security_domain: network
tests:
- name: True Positive Test
attack_data:
- data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1071.004/long_dns_query/dns-sysmon.log
source: XmlWinEventLog:Microsoft-Windows-Sysmon/Operational
sourcetype: XmlWinEventLog
test_type: unit
Stages and Predicates
Stage 1: tstats
| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time) as lastTime FROM datamodel=Network_Resolution
WHERE NOT DNS.record_type IN ("Pointer","PTR","SOA", "SRV") DNS.query != *.
BY DNS.answer DNS.answer_count DNS.query
DNS.query_count DNS.reply_code_id DNS.src
DNS.vendor_product DNS.dest DNS.record_type
Stage 2: search
| `drop_dm_object_name("DNS")`
Stage 3: search
| `security_content_ctime(firstTime)`
Stage 4: search
| `security_content_ctime(lastTime)`
Stage 5: eval
| eval tlds=split(query,".")
Stage 6: eval
| eval tld=mvindex(tlds,-1)
Stage 7: eval
| eval tld_len=len(tld)
Stage 8: search
| search tld_len<=20
Stage 9: eval
| eval query_length = len(query)
Stage 10: table
| table firstTime lastTime src dest query query_length record_type count record_type
Stage 11: eventstats
| eventstats stdev(query_length) AS stdev avg(query_length) AS avg p50(query_length) AS p50
Stage 12: where
| where query_length>(avg+stdev*2)
Stage 13: eval
| eval z_score=(query_length-avg)/stdev
Stage 14: stats
| stats count values(query) as query values(dest) as dest avg(query_length) as avg_query_length values(record_type) as record_type min(firstTime) as firstTime latest(lastTime) as lastTime
BY src
Stage 15: search
| `dns_query_length_with_high_standard_deviation_filter`
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
DNS.record_type | in | "PTR", "Pointer", "SOA", "SRV" |
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.