Passive vs active subdomain enumeration, and when each one lies
Passive enumeration finds names that no longer resolve. Active enumeration misses names nobody ever published. A practical guide to combining both without trusting either.
Subdomain enumeration splits into two families, and almost every recon methodology you will read picks one and treats the other as a footnote. That is backwards. The two techniques fail in opposite directions, and the only reliable way to map a domain is to run both and then distrust both.
The two families
Passive enumeration asks somebody else what names exist. Certificate transparency logs, passive DNS aggregators, search indexes, ASN and WHOIS pivots, public code and package registries. You never send a packet to the target.
Active enumeration asks the target's own DNS. You generate candidate names — from a wordlist, from permutations of names you already have, from a zone transfer if someone left one open — and you resolve them.
The first is quiet and fast and finds things nobody meant to publish. The second is noisy and slow and finds things nobody ever published at all.
How passive enumeration lies
It returns names that no longer resolve
A certificate issued in 2021 stays in the transparency log forever. The host it was issued for may have been decommissioned in 2022. Passive sources are append-only archives, not a current inventory, and treating a CT dump as "the attack surface" will hand you a list where a meaningful fraction of entries are simply gone.
This is not a small effect. On a long-lived domain with automated certificate issuance, expect a large share of CT-derived names to be dead, expired staging environments, or per-deploy hostnames that existed for an afternoon.
It only sees names that got a certificate
Certificate transparency is the single richest passive source, and its blind spot is structural: it records names that appear in a publicly-trusted TLS certificate. An internal service behind a private CA, a plain-HTTP host, a name that only ever appears as a CNAME target, a wildcard-covered subdomain — none of these generate a CT entry.
A wildcard is the worst case.
*.internal.example.com produces one log entry and hides every name under it.
Its freshness is somebody else's problem
Passive DNS aggregators sample. They see what their sensors see, from where their sensors are, at whatever rate they collect. Two providers queried for the same domain on the same day routinely return different sets, and neither is wrong — they are answering a different question from the one you think you asked.
How active enumeration lies
Wildcard DNS turns every guess into a hit
If a zone answers *.example.com with an address, then definitely-not-real-a8f3.example.com resolves too, and a naive brute force reports every word in your list as a live subdomain.
Detect it before you trust a single result. Resolve a few names that cannot plausibly exist and see what comes back:
for i in 1 2 3; do
dig +short "$(head -c 16 /dev/urandom | base32 | tr -d = | tr 'A-Z' 'a-z').example.com"
done
If those return addresses, you are inside a wildcard and every positive needs a second signal — a differing HTTP response, a different certificate, a different response body hash — before it counts as a real host.
Note that a wildcard can be scoped to one branch of the tree.
*.dev.example.com may exist while example.com itself has no wildcard, so testing once at the apex is not enough.
Your wordlist is a statement about someone else's naming convention
Brute force finds api, staging, vpn and mail because those are in every list.
It does not find pf-edge-04 or tenant-7719 or jenkins-old-do-not-delete, and those are usually the interesting ones.
Permutation helps: take the names you already have and mutate them systematically — api becomes api-dev, api2, dev-api, api.stage.
The seed list matters more than the wordlist, which is the strongest argument for running passive enumeration first and feeding its output into the active pass rather than running the two independently.
Resolvers change the answer
Public resolvers rate-limit, truncate, cache negatives aggressively, and some return an ad-server address for NXDOMAIN. A brute force run through a bad resolver pool produces both false negatives (dropped under rate limiting) and false positives (NXDOMAIN hijacking).
Use resolvers you have verified against a known-good and known-bad name, and re-verify them during long runs.
Where they fail together
Both techniques answer "does this name exist in DNS", and neither answers "is there something here".
| Signal | Passive | Active | What it actually proves |
|---|---|---|---|
| Name appears in CT log | yes | no | A certificate was issued once |
| Name resolves | no | yes | A record exists today |
| Host answers on 80/443 | no | no | Something is listening |
| Response is distinct | no | no | It is not a wildcard or a parked catch-all |
The last two rows are where the real inventory starts, and neither enumeration technique gets you there. A name that resolves to an address with nothing listening is not attack surface. A name that resolves to a decommissioned cloud bucket, on the other hand, is a subdomain takeover waiting to happen, and you only see the difference by probing.
A sequence that works
- Passive first, widely. Multiple sources, union the results, do not deduplicate away the odd ones.
- Detect wildcards at the apex and at every branch you found, before anything else.
- Seed the active pass with the passive results, not with a generic wordlist alone. Permute what you found.
- Resolve everything through verified resolvers, and record the full CNAME chain rather than just the final address. The chain is what reveals a dangling delegation.
- Probe what resolved. Status, title, response hash, certificate subject. This is the step that separates a name list from an inventory.
- Diff against last time. The single highest-value output of recon is not the list, it is what changed since the previous run.
Step six is the one most methodologies omit and the one that pays off repeatedly. A first-time enumeration of a large domain gives you thousands of names and no sense of which matter. The same enumeration a week later gives you eleven new names, and those eleven are worth reading one by one.
Before you run any of this
Active enumeration sends traffic to infrastructure you do not own. Passive enumeration does not, which is why it is often assumed to be always-permitted — but the output is still a map of someone else's estate, and what you do with it is scoped by the same authorisation.
Get the scope in writing. Know which names are in it and which are shared tenancy that belongs to a provider rather than your client. The interesting finding is worth nothing if you found it outside the boundary.