Subdomain Enumeration
For each in-scope root domain you want every subdomain you can find. There are three techniques and they don’t fully overlap — I run all three, because each one finds names the others miss.
| Technique | Source | Finds |
|---|---|---|
| Passive | OSINT APIs, search engines, cert logs | Known/indexed names, zero target traffic |
| Active | DNS queries against the target’s resolvers | Names that resolve but aren’t indexed |
| Brute force | Wordlist against a resolver | Predictable names (dev, vpn, staging) |
Passive: subfinder + amass
I usually start with subfinder (ProjectDiscovery) for passive enum — it’s
fast. amass pulls from a different (overlapping) set of sources, so I run both
and merge the results.
# subfinder against every root at once
subfinder -dL scope-domains.txt -all -silent \
| tee recon/domains/subfinder.txt
# amass passive enum, per root
while read -r domain; do
amass enum -passive -d "$domain" -o "recon/domains/$domain/amass-passive.txt"
done < scope-domains.txt
You’ll get a lot more out of passive sources by adding API keys (Censys,
SecurityTrails, Shodan, VirusTotal, GitHub, etc.) to
~/.config/subfinder/provider-config.yaml and ~/.config/amass/config.ini.
Keyed sources roughly double the yield.
Active: amass
Active enumeration resolves and validates names against the target’s own DNS, catching wildcards and names that exist but aren’t in any OSINT feed.
while read -r domain; do
amass enum -active -d "$domain" -o "recon/domains/$domain/amass-active.txt"
done < scope-domains.txt
Brute force: predictable names
Brute forcing throws a wordlist of common labels at the domain. I brute with a
dedicated resolver tool (puredns or
dnsx, covered on the DNS Resolution page) rather than
amass’s built-in brute, because you control the rate and the resolver quality:
# Generate candidates from a wordlist, then resolve them
dnsx -d example.com -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt \
-silent -o recon/domains/example.com/brute.txt
amass enum -brute -d example.com does the same thing in one shot if you’d
rather keep it simple.
Certificate transparency (crt.sh)
Public CT logs are one of the best passive sources — every TLS cert a host has ever requested is logged with its names. Query directly:
curl -s "https://crt.sh/?q=%25.example.com&output=json" \
| jq -r '.[].name_value' \
| sed 's/^\*\.//' | tr 'A-Z' 'a-z' | sort -u \
| tee recon/domains/example.com/crtsh.txt
Merge, validate, and scope
Combine every source, strip the noise, and filter against your blacklist. This is the step that keeps you in scope:
cat recon/domains/example.com/*.txt recon/domains/subfinder.txt \
| sed 's/^\*\.//;s/\.$//' | tr 'A-Z' 'a-z' \
| grep -E '^[a-z0-9_.-]+$' \
| grep -E '\.example\.com$' \
| grep -vEf blacklist.txt \
| sort -u \
| tee recon/domains/example.com/subdomains.txt
anew is handy here — it appends only
new lines to a file and prints them, so you can see what each run adds:
subfinder -d example.com -silent | anew recon/domains/example.com/subdomains.txt
The output subdomains.txt is the input to DNS Resolution,
where you find out which of these names are actually live.