Detect DNS Subdomain Wordlist Brute-Force Enumeration in Microsoft Sentinel
Adversaries iteratively query an organization's authoritative or recursive DNS infrastructure with large wordlists of candidate subdomain labels (dev, staging, vpn, mail-old, api-internal, etc.) to discover hostnames that were never intentionally published — forgotten staging environments, internal-only services accidentally exposed, or third-party integrations. Unlike the HTTP directory/file wordlist scanning covered by generic Active Scanning detections, this technique operates entirely at the DNS layer using tools such as massdns, puredns, gobuster's dns mode, dnsrecon, fierce, and amass's active enumeration mode, which resolve thousands of candidate FQDNs per minute and rely on the high NXDOMAIN response rate as an intrinsic side effect of brute-forcing an unknown namespace. Because these queries never touch a web server or generate an HTTP log line, they are invisible to WAF- and web-access-log-based scanning detections and require dedicated visibility into DNS server query logs (authoritative nameserver, recursive resolver, or DNS security gateway) plus endpoint process telemetry for internal pivot scanning.
MITRE ATT&CK
- Tactic
- Reconnaissance
KQL Detection Query
let SubdomainWordlistTools = dynamic([
"massdns", "gobuster", "puredns", "dnsrecon", "fierce", "amass",
"dnsx", "altdns", "dnscan", "subbrute", "assetfinder"
]);
// Branch 1: Authoritative/recursive DNS server query logs (BIND, PowerDNS, Unbound, Infoblox via syslog/CEF)
let DNSWordlistBruteforce =
CommonSecurityLog
| where TimeGenerated > ago(24h)
| where DeviceEventClassID has_any ("dns", "query") or Activity has_any ("DNS Query", "Query")
| where Message has "NXDOMAIN" or AdditionalExtensions has "NXDOMAIN"
| extend QueriedFQDN = extract(@"query:\s*([a-zA-Z0-9\-\.]+)", 1, Message)
| where isnotempty(QueriedFQDN)
| extend BaseDomain = extract(@"([a-zA-Z0-9\-]+\.[a-zA-Z]{2,})$", 1, QueriedFQDN)
| summarize
NXDomainCount = count(),
UniqueSubdomains = dcount(QueriedFQDN),
UniqueBaseDomains = dcount(BaseDomain),
SampleSubdomains = make_set(QueriedFQDN, 20),
FirstQuery = min(TimeGenerated),
LastQuery = max(TimeGenerated)
by SourceIP, BaseDomain, DeviceVendor, DeviceProduct
| where UniqueSubdomains >= 50 and NXDomainCount >= 50
| extend
DetectionBranch = "DNSServerQueryLog",
ScanDurationMinutes = datetime_diff('minute', LastQuery, FirstQuery),
QueryRatePerMinute = round(toreal(NXDomainCount) / iff(datetime_diff('minute', LastQuery, FirstQuery) > 0, datetime_diff('minute', LastQuery, FirstQuery), 1), 1)
| project FirstQuery, LastQuery, ScanDurationMinutes, SourceIP, BaseDomain,
NXDomainCount, UniqueSubdomains, QueryRatePerMinute, SampleSubdomains,
DeviceVendor, DeviceProduct, DetectionBranch;
// Branch 2: Internal endpoint executing DNS subdomain wordlist/brute-force tooling
let InternalToolExecution =
DeviceProcessEvents
| where Timestamp > ago(24h)
| where FileName has_any (SubdomainWordlistTools)
or ProcessCommandLine has_any (
"massdns -r", "gobuster dns", "puredns bruteforce", "dnsrecon -D",
"fierce --domain", "amass enum -active", "dnsx -d", "altdns -i")
| summarize
AlertCount = count(),
Commands = make_set(ProcessCommandLine, 5),
Tools = make_set(FileName, 10),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp)
by AccountName, DeviceName, FileName
| extend
DetectionBranch = "InternalDNSEnumTool",
SourceIP = DeviceName,
BaseDomain = "internal-pivot",
NXDomainCount = AlertCount,
UniqueSubdomains = AlertCount,
QueryRatePerMinute = 0.0,
SampleSubdomains = Commands,
DeviceVendor = "MicrosoftDefenderEndpoint",
DeviceProduct = FileName,
ScanDurationMinutes = datetime_diff('minute', LastSeen, FirstSeen)
| project FirstQuery = FirstSeen, LastQuery = LastSeen, ScanDurationMinutes, SourceIP, BaseDomain,
NXDomainCount, UniqueSubdomains, QueryRatePerMinute, SampleSubdomains,
DeviceVendor, DeviceProduct, DetectionBranch;
DNSWordlistBruteforce
| union InternalToolExecution
| sort by NXDomainCount desc Detects DNS subdomain wordlist brute-forcing through two complementary branches. Branch 1 queries CommonSecurityLog for CEF-normalized DNS server query events (authoritative nameserver, recursive resolver, or DNS security gateway such as Infoblox) and flags source IPs generating a high volume of NXDOMAIN responses across many distinct subdomains of the same base domain within a rolling window — the intrinsic signature of brute-forcing an unknown DNS namespace with a wordlist. Branch 2 queries DeviceProcessEvents for execution of named DNS subdomain enumeration tools (massdns, gobuster dns mode, puredns, dnsrecon, fierce, amass active mode, dnsx, altdns) on monitored endpoints, covering internal pivot scanning or red team activity. Results are unioned for a unified triage view across both the DNS-server and endpoint-process detection surfaces.
Data Sources
Required Tables
False Positives & Tuning
- Authorized attack-surface management or external ASM vendors (e.g., contracted bug bounty platforms) running scheduled subdomain discovery scans from documented IP ranges
- Internal DNS infrastructure teams or DevOps performing zone audits, cache-warming scripts, or DNS migration validation that queries many candidate hostnames in sequence
- Approved penetration testing or red team engagements actively enumerating the DNS namespace during the engagement window
- Certificate transparency monitoring or subdomain-discovery SaaS tooling that resolves large candidate lists to validate live hosts before issuing scan reports
- Misconfigured internal applications or scripts performing rapid repeated DNS lookups against a typo'd or deprecated base domain (self-inflicted NXDOMAIN storms, not adversarial)
Other platforms for THREAT-Recon-DNSSubdomainWordlistEnumeration
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 1Simulated DNS Subdomain Wordlist Query Burst
Expected signal: Authoritative/recursive DNS server query log entries showing 60 sequential NXDOMAIN responses for distinct subdomains of example.com from the executing host's source IP within a short window.
- Test 2gobuster DNS Mode Execution
Expected signal: Process creation event for gobuster with CommandLine containing 'dns -d example.com -w' and the wordlist file path.
- Test 3massdns Bulk Subdomain Resolution
Expected signal: Process creation event for massdns with CommandLine containing '-r' and '-t A' flags; DNS query log entries for the listed candidate subdomains.
Response Playbook
Triage
- Identify the source IP of the DNS queries — run reverse DNS lookup, check IP reputation via AbuseIPDB and VirusTotal, determine ASN and hosting provider. Match against any documented authorized scanner or ASM vendor IP allowlist before escalating.
- Review SampleSubdomains for the base domain under enumeration — look for candidate labels targeting high-value environments (dev, staging, vpn, admin, internal, api-old, backup) which reveal adversary intent and what infrastructure they hope to uncover.
- Check for any successful (non-NXDOMAIN) resolutions mixed into the same query burst — a wordlist scan that resolves even a handful of real hostnames indicates the adversary has discovered live infrastructure and should be escalated immediately.
- Assess query velocity using QueryRatePerMinute — sustained rates in the hundreds to thousands of queries per minute indicate tooling such as massdns or puredns with high concurrency; low, evenly spaced rates may indicate an evasion-aware actor deliberately staying under resolver rate limits.
- Cross-reference the source IP or internal host against other reconnaissance signals (T1595.001 IP block scanning, T1595.002 vulnerability scanning, HTTP wordlist scanning) within the same timeframe — coordinated multi-vector reconnaissance indicates a more capable, targeted adversary.
- If the detection branch is InternalDNSEnumTool, determine whether the tool execution was authorized (internal red team, security engineering asset inventory) or represents a compromised host being used to enumerate internal or third-party DNS namespaces.
Containment
- If the scan is ongoing from an external source: rate-limit or block the source IP at the recursive resolver, DNS security gateway, or upstream firewall — document the block with incident reference number and review date.
- If any subdomains were successfully resolved during the scan: inventory those hosts immediately, confirm they are intentionally published and appropriately access-controlled, and decommission or firewall off any forgotten staging/internal assets discovered.
- Enable or tighten DNS response rate limiting (RRL) on authoritative nameservers to throttle high-volume NXDOMAIN-generating sources without impacting legitimate resolution traffic.
- If InternalDNSEnumTool fired on a managed endpoint without an approved change ticket: isolate the endpoint via EDR and treat as a potential compromised-host indicator pending investigation.
- Preserve raw DNS server query logs for the incident timeframe before log rotation — DNS query logs often have short retention windows and are the primary evidence source for this technique.
Evidence Collection
- Authoritative/recursive DNS server query logs (BIND named.log query logging, PowerDNS query log, Unbound query log, Infoblox syslog/CEF export) covering the full scan window with a buffer before and after
- DNS security gateway or resolver analytics (Cisco Umbrella, Cloudflare DNS Analytics, Infoblox Threat Insight) for aggregate query volume and NXDOMAIN ratio by source
- DeviceProcessEvents / Sysmon Event ID 1 for the flagged endpoint: full command line, parent process, and account context of the DNS enumeration tool invocation
- Zone transfer and DNS change history for the affected base domain — confirms whether any newly discovered subdomains were recently created, decommissioned, or misconfigured
- Threat intelligence enrichment (IP reputation, ASN, WHOIS, TI feed hits) for the source IP, documented in the incident ticket for attribution and future blocking decisions
Escalation Criteria
- !Any successful (non-NXDOMAIN) resolution occurred during the enumeration burst, indicating the adversary discovered live, previously unpublished infrastructure
- !Discovered subdomains correspond to sensitive environments — staging/dev copies of production systems, internal admin panels, VPN endpoints, or forgotten third-party integrations
- !Post-enumeration follow-on activity observed against a newly discovered hostname — HTTP requests, TLS handshakes, or login attempts within hours of the DNS brute-force burst
- !InternalDNSEnumTool execution occurred on an endpoint without a documented change ticket or approved red team engagement window
- !The same source IP or internal host also triggers T1595.001 (IP block scanning) or T1595.002 (vulnerability scanning) signals within the same window, indicating a coordinated multi-phase reconnaissance campaign
Investigation Guide
Related Techniques
Forensic Artifacts
- >
BIND query log (named.conf logging { channel query_log; }) or PowerDNS/Unbound query logs recording every queried FQDN and response code - >
Infoblox syslog/CEF export or DNS security gateway analytics with per-source query volume and NXDOMAIN ratio - >
Windows Prefetch: GOBUSTER.EXE-*.pf, MASSDNS.EXE-*.pf, AMASS.EXE-*.pf confirming tool execution timestamps - >
Bash/zsh history (Linux/macOS): massdns, puredns, dnsrecon, fierce, or amass command invocations with wordlist file paths - >
DNS resolver cache or recursive server telemetry showing burst resolution activity correlated to the flagged source IP
Tuning Guidance
The primary NXDOMAIN volume/cardinality threshold (50 unique subdomains and 50 NXDOMAIN responses within 24h) is a starting baseline — tune per environment based on normal DNS query patterns, since organizations with many legitimate short-lived subdomains (ephemeral CI/CD environments, feature-branch preview URLs) may need a higher threshold or an allowlist for known dynamic-DNS naming conventions. Exclude documented ASM vendor and internal DevOps source IPs by allowlist rather than raising the global threshold, to avoid masking genuine low-volume adversary activity. The endpoint process branch is high-fidelity and should rarely need tuning beyond excluding security engineering hosts that run these same tools for authorized internal asset inventory — track those by device group or account name rather than suppressing the tool signatures entirely.
Hunting Queries
Hunts for slow, low-volume DNS subdomain enumeration spread across a wide time window (6+ hours) to evade the primary detection's per-window volume threshold — a pattern used by evasion-aware adversaries deliberately pacing queries below typical rate-limit or alerting thresholds.
// Hunt for low-and-slow DNS subdomain enumeration evading the primary volume threshold
CommonSecurityLog
| where TimeGenerated > ago(7d)
| where Message has "NXDOMAIN" or AdditionalExtensions has "NXDOMAIN"
| extend QueriedFQDN = extract(@"query:\s*([a-zA-Z0-9\-\.]+)", 1, Message)
| where isnotempty(QueriedFQDN)
| extend BaseDomain = extract(@"([a-zA-Z0-9\-]+\.[a-zA-Z]{2,})$", 1, QueriedFQDN)
| summarize UniqueSubdomains = dcount(QueriedFQDN), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, BaseDomain
| where UniqueSubdomains >= 30 and datetime_diff('hour', LastSeen, FirstSeen) >= 6
| sort by UniqueSubdomains desc search index=dns sourcetype IN ("bind:query", "infoblox:dns", "powerdns", "unbound") NXDOMAIN earliest=-7d
| rex field=_raw "query:\s*(?<queried_fqdn>[a-zA-Z0-9\-\.]+)"
| rex field=queried_fqdn "(?<base_domain>[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}$)"
| stats dc(queried_fqdn) as unique_subdomains, min(_time) as first_seen, max(_time) as last_seen by src_ip, base_domain
| eval hours_span=round((last_seen - first_seen) / 3600, 1)
| where unique_subdomains >= 30 AND hours_span >= 6
| sort - unique_subdomains Atomic Red Team Tests
Issues a rapid sequence of DNS queries for randomly-prefixed subdomains of a test domain, simulating the NXDOMAIN-heavy query pattern generated by wordlist-based subdomain brute-forcing tools. No actual scanning infrastructure is used.
Command
for i in $(seq 1 60); do dig +short "testlabel${i}-nonexistent-subdomain.example.com" > /dev/null; done Expected Telemetry
Authoritative/recursive DNS server query log entries showing 60 sequential NXDOMAIN responses for distinct subdomains of example.com from the executing host's source IP within a short window.
Expected Detection
KQL/SPL Branch 'DNSServerQueryLog' fires once UniqueSubdomains and NXDomainCount cross the 50-query threshold within the observation window.
Executes gobuster in DNS enumeration mode against a test domain using a small wordlist, generating the process-level telemetry and command-line pattern associated with active subdomain brute-forcing tooling.
Command
echo -e "dev\nstaging\ntest\nvpn\nadmin" > /tmp/atomic-dns-wordlist.txt && gobuster dns -d example.com -w /tmp/atomic-dns-wordlist.txt -q Cleanup
rm -f /tmp/atomic-dns-wordlist.txt Expected Telemetry
Process creation event for gobuster with CommandLine containing 'dns -d example.com -w' and the wordlist file path.
Expected Detection
KQL/SPL Branch 'InternalDNSEnumTool' fires on FileName/Image match for gobuster combined with the 'gobuster dns' command-line pattern.
Runs massdns against a small candidate subdomain list to simulate the high-throughput bulk DNS resolution pattern used by adversaries to brute-force large subdomain wordlists in minutes.
Command
echo -e "dev.example.com\nstaging.example.com\napi-old.example.com" > /tmp/atomic-massdns-input.txt && massdns -r /etc/resolv.conf -t A -o S /tmp/atomic-massdns-input.txt 2>/dev/null || echo massdns-not-installed Cleanup
rm -f /tmp/atomic-massdns-input.txt Expected Telemetry
Process creation event for massdns with CommandLine containing '-r' and '-t A' flags; DNS query log entries for the listed candidate subdomains.
Expected Detection
KQL/SPL Branch 'InternalDNSEnumTool' fires on FileName match for massdns combined with the '-r' resolver-file flag pattern.