Detection rules › Splunk

DNS Query Length With High Standard Deviation

Status
production
Severity
low
Group by
DNS.answer_count, DNS.query_count, DNS.vendor_product, QueryName, dest_ip, dns.answers.name, dns.answers.type, dns.response.code, src, src_ip
Author
Bhavin Patel, Splunk
Source
github.com/splunk/security_content

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.

Known false positives

  • It's possible there can be long domain names that are legitimate.

MITRE ATT&CK coverage

Telemetry coverage

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

Rule body

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: []
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

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

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DNS.record_typein"PTR", "Pointer", "SOA", "SRV"excludes:DNS.record_type

Indicators

These rows show field, operator, and value matches.