Subdomain Enumeration

Passive, active, and brute-force discovery of subdomains for each in-scope root.

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.

TechniqueSourceFinds
PassiveOSINT APIs, search engines, cert logsKnown/indexed names, zero target traffic
ActiveDNS queries against the target’s resolversNames that resolve but aren’t indexed
Brute forceWordlist against a resolverPredictable 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.

Last modified July 4, 2026: Post/mobi (#71) (ff64902)