Detect Subdomain Bruteforce Causing NXDOMAIN Storm in Splunk
Before selecting a phishing pretext, staging infrastructure, or an external-facing service to target, adversaries frequently brute-force an organization's DNS namespace with a wordlist of common subdomain labels (vpn, mail, sso, dev, staging, api, remote, citrix, owa, etc.) to enumerate hosts that are not otherwise advertised. Tools such as dnsx, massdns, puredns, gobuster (dns mode), dnsrecon, and fierce resolve tens of thousands of candidate labels per minute against the target's authoritative or recursive DNS servers. Because most guessed labels do not correspond to a real record, this activity produces a highly distinctive footprint at the DNS server: a single source issuing an extremely high volume of queries against the organization's domain(s) within a short window, the overwhelming majority of which return NXDOMAIN, with very little repetition between query names (each guess is unique, unlike a caching resolver replaying the same handful of hostnames). This is distinct from the broader DNS/passive-DNS reconnaissance techniques already covered on the parent T1596.001 page (which include zone-transfer attempts and tool-execution detection) in that it isolates the specific volumetric NXDOMAIN-ratio signature that a bruteforce sweep leaves on the DNS server itself, catching enumeration even when the querying tool cannot be observed on an endpoint (e.g., run from an external host, a compromised third-party network, or a VPS with no EDR visibility) and regardless of which specific tool was used to generate the wordlist traffic. Scattered Spider and other access brokers routinely run this style of sweep against a target's public domain ahead of helpdesk-vishing and SSO-phishing campaigns to build a list of live internal-sounding hostnames to reference for pretext and targeting.
MITRE ATT&CK
- Tactic
- Reconnaissance
SPL Detection Query
index=dns sourcetype=stream:dns
| eval QueryType=upper(coalesce(query_type, qtype_name, ""))
| eval QueryName=coalesce(query, query_name, name, "")
| eval SourceIP=coalesce(src_ip, src, client_ip)
| eval ResponseCode=upper(coalesce(rcode_name, reply_code, ""))
| where QueryType IN ("A", "AAAA", "CNAME", "MX", "TXT", "SRV", "NS")
| bin _time span=15m
| stats count AS TotalQueries,
dc(QueryName) AS UniqueSubdomains,
count(eval(ResponseCode="NXDOMAIN")) AS NxDomainCount,
values(QueryType) AS QueryTypes
BY _time, SourceIP
| eval NxDomainRatio=round(NxDomainCount/TotalQueries, 2)
| eval UniqueLabelRatio=round(UniqueSubdomains/TotalQueries, 2)
| where TotalQueries >= 200 AND NxDomainRatio >= 0.85 AND UniqueLabelRatio >= 0.90
| eval ThreatType="SubdomainBruteforce_NXDOMAINStorm"
| eval Severity=if(TotalQueries >= 2000, "HIGH", "MEDIUM")
| table _time, SourceIP, TotalQueries, UniqueSubdomains, NxDomainRatio, UniqueLabelRatio, QueryTypes, Severity
| sort - TotalQueries SPL detection against Splunk Stream's stream:dns sourcetype (or an equivalent DNS query log) applying the same NXDOMAIN-storm fingerprint as the KQL query: 200+ queries from one source IP within a 15-minute bucket, an NXDOMAIN ratio of 85%+, and a unique-query-name ratio of 90%+. Field coalescing handles both Splunk Stream and Zeek/Bro-style field naming.
Data Sources
Required Sourcetypes
False Positives & Tuning
- Scheduled attack-surface management scans against the organization's own domains
- Authorized red team/pentest subdomain enumeration
- Misconfigured internal clients retrying stale or typo'd hostnames at volume
Other platforms for THREAT-Recon-SubdomainBruteforceNXDOMAINStorm
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Subdomain Bruteforce Simulation via dnsx
Expected signal: DNS server query logs record several hundred queries within a short window from the test source IP, with the large majority returning NXDOMAIN and each query name unique (near-1:1 unique-name-to-query ratio).
- Test 2High-Speed Bruteforce Simulation via massdns
Expected signal: DNS server logs show a sharp spike in query volume from the test source IP within seconds to minutes, with the overwhelming majority of responses being NXDOMAIN.
- Test 3Low-and-Slow Throttled Enumeration Simulation
Expected signal: DNS server logs record roughly 80 queries spread over about 40 minutes from the test source IP, nearly all returning NXDOMAIN, each with a unique query name.
Response Playbook
Triage
- Confirm the DNS server(s) receiving the flagged volume are the organization's own authoritative or recursive nameservers, and identify which of the organization's registered domains the swept queries target.
- Pull a sample of the actual query names (not just aggregate counts) — a wordlist pattern (vpn.<domain>, sso.<domain>, dev.<domain>, api.<domain>, mail.<domain>) confirms bruteforce enumeration rather than a benign traffic anomaly.
- Check the source IP's reputation and infrastructure type (AbuseIPDB, Shodan, VirusTotal, WHOIS). Bruteforce sweeps commonly originate from VPS hosting or scanning-as-a-service infrastructure (Censys, Shodan, BinaryEdge) rather than residential ranges.
- Determine whether the source IP correlates with a known authorized scanner (attack-surface-management vendor, internal red team, vulnerability management platform) before treating as hostile.
- Identify any subdomains in the sweep that DID resolve (the small non-NXDOMAIN remainder) — these are the hostnames the adversary successfully discovered and are likely to be referenced in a follow-on phishing or access attempt.
- Check whether the same source IP or infrastructure cluster has swept other organizations you have shared visibility into (ISAC feeds, threat intel), indicating a broad, non-targeted reconnaissance campaign versus one aimed specifically at this organization.
Containment
- Block the sweeping source IP at the DNS server ACL and at the network perimeter/WAF.
- Enable or tighten response-rate limiting (RRL) on authoritative/recursive DNS servers so a single source cannot generate unbounded query volume.
- For any newly discovered live hostnames the sweep surfaced (the resolving remainder), verify they are intended to be externally reachable; decommission or restrict access to any stale/forgotten hosts (old staging, dev, or admin panels) uncovered by the enumeration.
- If the sweep is tied to a broader campaign (e.g., preceding Scattered Spider-style helpdesk vishing), notify the helpdesk/IT support team to expect social-engineering attempts referencing the discovered hostnames.
Evidence Collection
- Full DNS query log export (query name, response code, timestamp) for the flagged source IP across the sweep window
- List of query names that resolved successfully (non-NXDOMAIN) during the sweep, to identify what infrastructure the adversary now knows exists
- Threat intelligence enrichment on the source IP/ASN and hosting provider
- DNS server RRL/rate-limit logs, if enabled, showing whether queries were throttled or dropped
Escalation Criteria
- !The sweep surfaces a live (resolving) subdomain that exposes an unintended or forgotten service (staging environment, admin panel, legacy VPN endpoint)
- !The sweep is immediately followed by authentication attempts, phishing, or vishing activity referencing one of the discovered hostnames
- !The same source infrastructure is observed sweeping multiple business units, subsidiaries, or partner organizations, indicating a large-scale reconnaissance campaign
- !Enumeration traffic overlaps in time with other reconnaissance techniques (WHOIS lookups, certificate transparency monitoring, employee OSINT) against the same organization, suggesting a coordinated targeting effort
Investigation Guide
Related Techniques
Forensic Artifacts
- >
DNS server query logs (DnsEvents / Windows DNS Server analytical log / Zeek dns.log) showing per-query name, type, response code, and source IP - >
Source IP/ASN reputation and hosting-provider classification - >
Response-rate-limiting or DNS firewall logs indicating throttled or blocked query volume - >
Any resolving (non-NXDOMAIN) query names discovered during the sweep, cross-referenced against the organization's asset inventory
Tuning Guidance
The 200-query / 85% NXDOMAIN / 90% unique-label thresholds are tuned for a mid-size organization's DNS estate over a 15-minute window. High-traffic public resolvers or CDN-fronted domains may need the volume threshold raised substantially to avoid false positives from legitimate high-QPS clients; conversely, small organizations with low baseline DNS traffic can lower the volume threshold to 50-100 to catch throttled, low-and-slow sweeps designed to evade rate limiting. Always allowlist known attack-surface-management vendors and authorized scanning source IPs by exact address rather than by raising thresholds, since a raised threshold risks missing a genuinely throttled adversary sweep. The unique-label ratio is the strongest secondary signal: a caching resolver replaying a small set of real hostnames will have a low ratio, while a wordlist-driven bruteforce tool generates a near-1:1 ratio because almost every guess is unique.
Hunting Queries
Broader 7-day hunt using a lower query-volume threshold (50) and relaxed NXDOMAIN ratio (0.70) over wider 1-hour buckets to surface slower, lower-and-slower subdomain enumeration that stays under the main detection's 15-minute/200-query thresholds.
DnsEvents
| where TimeGenerated > ago(7d)
| where QueryType in ("A", "AAAA", "CNAME", "MX", "TXT", "SRV", "NS")
| summarize
TotalQueries = count(),
UniqueSubdomains = dcount(Name),
NxDomainCount = countif(ResultCode =~ "NXDOMAIN")
by ClientIP, bin(TimeGenerated, 1h)
| extend NxDomainRatio = round(NxDomainCount * 1.0 / TotalQueries, 2)
| extend UniqueLabelRatio = round(UniqueSubdomains * 1.0 / TotalQueries, 2)
| where TotalQueries >= 50 and NxDomainRatio >= 0.70
| sort by TotalQueries desc index=dns sourcetype=stream:dns
| eval QueryName=coalesce(query, query_name, name, "")
| eval SourceIP=coalesce(src_ip, src, client_ip)
| eval ResponseCode=upper(coalesce(rcode_name, reply_code, ""))
| bin _time span=1h
| stats count AS TotalQueries, dc(QueryName) AS UniqueSubdomains,
count(eval(ResponseCode="NXDOMAIN")) AS NxDomainCount
BY SourceIP, _time
| eval NxDomainRatio=round(NxDomainCount/TotalQueries, 2)
| eval UniqueLabelRatio=round(UniqueSubdomains/TotalQueries, 2)
| where TotalQueries >= 50 AND NxDomainRatio >= 0.70
| sort - TotalQueries Atomic Red Team Tests
Simulates a wordlist-driven subdomain enumeration sweep using dnsx against a lab domain, generating a high volume of queries where the majority of guessed labels do not exist. Tests detection of the high-volume, high-NXDOMAIN-ratio, high-unique-label-ratio signature.
Command
cat wordlist.txt | sed 's/$/.<LAB_DOMAIN>/' | dnsx -silent -resp -rcode -t 100 > dnsx_results.txt Cleanup
rm -f dnsx_results.txt Expected Telemetry
DNS server query logs record several hundred queries within a short window from the test source IP, with the large majority returning NXDOMAIN and each query name unique (near-1:1 unique-name-to-query ratio).
Expected Detection
Alert fires when TotalQueries >= 200, NxDomainRatio >= 0.85, and UniqueLabelRatio >= 0.90 within the 15-minute aggregation window (lower thresholds in a lab tenant to validate against a smaller wordlist).
Replays a high-throughput subdomain resolution sweep using massdns against a mock wordlist, mimicking the volumetric query pattern of a fast bruteforce tool rather than a slower single-threaded scanner.
Command
awk '{print $1".""<LAB_DOMAIN>"}' wordlist.txt | massdns -r resolvers.txt -t A -o S -w massdns_output.txt Cleanup
rm -f massdns_output.txt Expected Telemetry
DNS server logs show a sharp spike in query volume from the test source IP within seconds to minutes, with the overwhelming majority of responses being NXDOMAIN.
Expected Detection
Alert fires on the aggregate TotalQueries/NxDomainRatio thresholds from the source IP; the Severity field escalates to High if volume exceeds 2000 queries in the window.
Simulates a rate-limit-evading subdomain sweep that throttles query rate to stay below common per-minute alerting thresholds, validating the hunting query's relaxed thresholds over a wider window.
Command
python3 -c "
import socket, time
labels = [f'test{i}' for i in range(1, 80)]
for label in labels:
try:
socket.gethostbyname(f'{label}.<LAB_DOMAIN>')
except socket.gaierror:
pass
time.sleep(30)
" Expected Telemetry
DNS server logs record roughly 80 queries spread over about 40 minutes from the test source IP, nearly all returning NXDOMAIN, each with a unique query name.
Expected Detection
Falls below the primary 15-minute/200-query detection but should be surfaced by the tuning-guidance hunting query using the relaxed 50-query/70% NXDOMAIN/1-hour thresholds.