Subdomain Enumeration: Passive & Active Recon
What is a subdomain, and why hunt for them?
A subdomain is an extension of a company's main domain, used to split off a specific part of their online presence: api.acme.com, dev.acme.com, blog.acme.com. The root domain acme.com might be a polished, well-defended marketing site, while dev-portal.acme.com is a forgotten test server nobody patched in two years.
Every subdomain is a separate door into the organisation, sometimes an entirely different application, a different team, a different (often weaker) level of security. Before a single exploit is attempted, mapping every one of these doors is what Subdomain Enumeration is for, and it is usually the very first step of a penetration test or bug bounty engagement.
Passive vs active
| Passive | Active | |
|---|---|---|
| Talks to the target? | No, queries third parties | Yes, sends DNS/HTTP to the target |
| Noise / detection | Silent | Can trip rate limits / WAFs |
| Finds | Anything ever made public | Hidden names not in public data |
Real recon uses both: passive first (free and silent), then active to fill the gaps.
Passive enumeration
Certificate Transparency: how crt.sh actually works
Every publicly trusted TLS certificate a Certificate Authority issues gets logged forever in a public, tamper-proof ledger called Certificate Transparency (CT). This was built so anyone could audit which certificates exist for a domain, but it doubles as a subdomain goldmine: if internal-api.acme.com ever got an HTTPS certificate, even briefly, it is permanently recorded, whether or not it still resolves today. crt.sh is simply a web search interface over these logs.
curl -s 'https://crt.sh/?q=%.acme.com&output=json' | jq -r '.[].name_value' | sort -u
CertSpotter offers the same CT log data but as a queryable API instead of a web page, useful for scripting:
curl -s 'https://api.certspotter.com/v1/issuances?domain=acme.com&include_subdomains=true&expand=dns_names'
How aggregator tools like subfinder actually find results
You might assume subfinder scans acme.com directly. It mostly doesn't. Tools like subfinder and amass are API aggregators: they fire off requests to dozens of third-party sources at once, CT log services, Shodan, BinaryEdge, SecurityTrails, and more, then merge and de-duplicate whatever comes back into one clean list. Because the data is already indexed on someone else's servers, this can map an organisation's public-facing surface in seconds without a single packet reaching the target itself.
subfinder -d acme.com -all -silent -o subfinder.txt
amass enum -passive -d acme.com -o amass.txt
assetfinder --subs-only acme.com
Search-engine dorking
Search engines crawl and index huge amounts of the internet, including subdomains you'd never guess. A dork query filters that index down to just the target:
site:*.acme.com -site:www.acme.com
This finds every indexed page on any acme.com subdomain while excluding the main www site, surfacing the unusual ones search engines happened to crawl.
ASN scanning: mapping the whole network, not just one domain
So far every technique starts from a domain name. ASN scanning flips the approach: it starts from the organisation's network ownership.
An ASN (Autonomous System Number) identifies a block of IP address space that one organisation controls on the internet. Large companies often own their own ASN. Once you find it, you can enumerate every IP address and domain hosted inside it, including infrastructure that has nothing to do with acme.com by name but is still owned and run by Acme.
amass intel -org "Acme" # find ASNs and netblocks belonging to the org
amass intel -asn 12345 # given an ASN, list its netblocks and domains
This is how you catch infrastructure that a plain subdomain scan would completely miss, a separate acquired company, a legacy brand, a raw IP with no DNS name pointing at it at all.
Subdomain vs virtual host, a distinction beginners miss
These two look similar in a URL but are found completely differently:
| Subdomain (DNS level) | Virtual Host (Server level) | |
|---|---|---|
| Where it lives | An A/CNAME record in public DNS | A routing rule inside Apache/Nginx config |
| Visibility | Visible to public DNS and OSINT tools | Often invisible to DNS, hidden from the outside |
| How it's found | Passive discovery (crt.sh, subfinder) | Active fuzzing (ffuf spraying the Host header) |
| What routes the request | The internet routes you to an IP by DNS | The web server then routes you to a folder/app by the Host header you sent |
In short: DNS gets you to the right server; the Host: header then tells that server which site to actually serve. A server can quietly host ten virtual hosts with no public DNS record pointing at nine of them, which is exactly why active vhost fuzzing (below) matters even after passive recon is done.
Active enumeration
DNS brute-forcing
Take a wordlist (see the wordlists lesson, the SecLists DNS lists are the standard) and test every word.acme.com. This finds names that were never published anywhere public, and were therefore invisible to every passive method above.
gobuster dns -d acme.com -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
puredns bruteforce wordlist.txt acme.com -r resolvers.txt
Permutations
Mutate names you've already found (api -> api-dev, api2, staging-api) with dnsgen, gotator, or altdns, then resolve the candidates. This catches predictable naming patterns a plain wordlist would never guess.
Virtual-host (vhost) fuzzing
Spray the Host: header against a single IP to reveal the hidden vhosts discussed above:
ffuf -u https://acme.com -H 'Host: FUZZ.acme.com' -w wordlist.txt -mc 200,403
Zone transfer (AXFR)
A misconfigured nameserver will hand you the entire DNS zone in one request, an instant, complete list:
dig axfr @ns1.acme.com acme.com
Most servers refuse this, but it is always worth one try. The public zonetransfer.me zone is left open on purpose so you can see a successful dump.
Resolve, then probe
A raw name list is full of dead entries. Two steps clean it up:
- Resolve which names actually point somewhere (
dnsx). - Probe which of those run a live web server (
httpx/httprobe).
cat subfinder.txt amass.txt | sort -u | dnsx -silent > resolved.txt
httpx -l resolved.txt -title -sc -ip
A [403] or [401] host is still alive, do not throw away non-200 results; locked-down admin panels are exactly what you want to find.
Try it (terminal)
subfinder -d acme.com, see the passive results.curl -s 'https://crt.sh/?q=%.acme.com', certificate transparency names.amass intel -org acme, map the ASN and netblocks the org owns.gobuster dns -d acme.com -w wordlist.txt, notice it findsinternal,jenkins,gitthat passive missed.ffuf -u https://FUZZ.acme.com -w wordlist.txt, vhost fuzzing.httpx -l subs.txt, probe which names are live web servers.dig axfr @ns1.acme.com acme.com(refused), thendig axfr @nsztm1.digi.ninja zonetransfer.me(leaks the zone).
Stay in scope
Active enumeration sends real traffic. Only test domains you are authorised to (your own, or those in a bug-bounty program's scope). Rate-limit brute force, respect robots.txt and program rules, or you will get your IP banned, or worse.
Why this matters
The biggest bugs are usually not on the main site, they are on the forgotten dev, staging, or admin subdomain nobody patched. Thorough subdomain enumeration, passive, active, and ASN-level, is the difference between testing one app and testing the company's whole exposed estate.