Every engagement starts with the same problem: you cannot test what you cannot find. Certificate transparency logs have made subdomain enumeration dramatically easier than it was five years ago, but the tooling only produces a list — turning that list into a reliable asset inventory is the actual work.
Why passive enumeration first
Active brute force against a client’s DNS is noisy and, depending on the contract, may not be authorised. Start passive:
- Certificate Transparency — every publicly trusted certificate is logged.
crt.shis the fastest source. - Passive DNS — datasets like SecurityTrails and VirusTotal expose historical resolutions.
- Search engines and code — API keys and hostnames leak into GitHub, Pastebin, and JS bundles.
Passive sources give you breadth with zero packets sent to the target.
The pipeline
I run two enumerators in parallel and merge, because they draw from overlapping but not identical sources.
# Passive collection
subfinder -d example.com -all -silent -o subfinder.txt
amass enum -passive -d example.com -o amass.txt
# Merge and deduplicate
cat subfinder.txt amass.txt | tr 'A-Z' 'a-z' | sort -u > all-subs.txt
wc -l all-subs.txt
Typical output for a mid-size organisation lands between 500 and 5,000 names. Most are dead.
Resolving quickly
massdns resolves tens of thousands of names per second if you give it a decent resolver list:
# Public resolvers, one per line
curl -s https://public-dns.info/nameservers.txt | head -2000 > resolvers.txt
massdns -r resolvers.txt -t A -o S -w resolved.txt all-subs.txt
Then filter to the ones that actually answer:
grep -E ' A ' resolved.txt | awk '{print $1}' | sed 's/\.$//' | sort -u > live.txt
Any name in resolved.txt that only ever returns CNAME is worth a second look — dangling CNAMEs pointing at deprovisioned cloud resources are a classic subdomain takeover.
HTTP probing
Resolution is not the same as serving. Probe for HTTP(S) and capture titles and status codes:
cat live.txt | httpx -silent -status-code -title -tech-detect -o http.txt
This is where the inventory becomes actionable: you end up with a list of hosts, their frameworks, and a status code. Login panels and default installs jump out immediately.
Keeping it current
Point-in-time enumeration goes stale within weeks. Schedule the whole pipeline weekly, diff against the previous run, and alert on new names:
comm -13 <(sort yesterday.txt) <(sort today.txt)
Newly appeared subdomains are the highest-value recon finding there is — they’re often staging environments that were never hardened because nobody remembers they exist.
Scope discipline
Two rules I hold to regardless of how interesting a finding looks:
- Everything found must be in scope. A subdomain resolving to a third-party SaaS provider is not your target, it’s their tenant.
- Record where each name came from. When a client asks how you found an internal hostname, “crt.sh, 2020-09-18” is an answer; “the tool found it” is not.