Subdomain Enumeration: Passive and Active Methods That Find More
A practical subdomain enumeration guide: pull names from certificate transparency, passive DNS and search APIs, brute force the rest, then resolve and probe to build a clean, de-duplicated asset list.
Subdomain enumeration is the step where a single root domain turns into a map of everything an organization has quietly put online. Most engagements start with one name in scope, and the hosts that actually get you somewhere are rarely the obvious ones. An old staging box, a forgotten marketing microsite, a dashboard that was never meant to face the internet: those come out of a good enumeration pass, not out of the main website. This guide walks the two halves of the job, passive collection from sources that already know the answer and active discovery that goes and asks, then shows how to resolve and probe the results into an asset list you can trust.
Read it top to bottom once to see how the pieces connect, then keep it nearby as a recipe. Every command here is aimed at a lab you own or a target you are authorized to assess.
Enumerate only domains you own or have written authorization to test. The examples use the project lab host, lab.0xpriv.dev. Scanning or resolving someone else's infrastructure without permission is not reconnaissance, it is trespass, and in many places it is a crime.
01What subdomain enumeration actually finds
At its core, subdomain enumeration is the process of discovering the hostnames that live under a parent domain. A company that owns example.com might also run api.example.com, mail.example.com, a dozen regional sites and a long tail of internal-looking names that leaked into public records. Each of those is a separate application, often on separate infrastructure, maintained by separate teams with separate patch schedules. Finding them is the difference between testing one website and testing the whole estate.
The work splits into collection and verification. Collection gathers candidate names from as many places as possible. Verification throws away the dead ones by resolving them in DNS and checking which answer on a web port. The goal is not a giant list, it is a short, accurate list of live hosts you can hand to the next phase without wasting time on names that no longer exist.
If you can run a command line tool and read a DNS record, you can follow this. No prior recon experience is assumed. Try each step against a domain you control so the output means something to you.
02Why subdomains expand the attack surface
Subdomains expand the attack surface because every new hostname is a new front door, and the doors nobody remembers are the ones left unlocked. The main site gets attention, code review and a WAF in front of it. The subdomain that a contractor spun up for a demo three years ago gets none of that, yet it may share a database, a session cookie or a trust relationship with the thing you actually care about.
Three patterns make subdomains worth the effort. First, variety: a single org can expose wildly different stacks across its names, and one of them is usually older and softer than the flagship. Second, environment leakage: dev, staging, uat and qa hosts tend to run debug features, weaker auth and real data. Third, dangling records, where a DNS entry still points at a cloud resource that was deleted, which can let someone else claim the name and serve content from it. That last case is subdomain takeover, and enumeration is how you find the candidates for it.
It is tempting to jump on the first interesting host you find. Resist it. Finish the enumeration pass first, because the asset you want is often three names further down the list, and a half-finished map leads you to test the wrong target.
03Passive versus active enumeration
Passive versus active is the first decision, and it comes down to who you talk to. Passive enumeration pulls names from third parties that have already observed them, so you never send a packet to the target. Active enumeration generates names yourself and asks the target's DNS whether they exist, which is louder but reaches hosts no public source has recorded.
Passive is where you start every time. It is quiet, fast and often returns most of the estate for free. Active fills the gaps passive leaves, especially internal naming schemes that were never logged anywhere public. A real pass uses both: passive to get the bulk, active to catch what is hiding from public datasets.
04Passive sources and the tools that query them
Passive sources are datasets that other systems built while the internet went about its business, and querying them hands you subdomains with zero noise toward the target. Three categories do most of the work, and a couple of tools stitch them together so you do not query each one by hand.
| Source | Why it knows your subdomains |
|---|---|
| Certificate Transparency logs | Every public TLS certificate is logged, and the certificate lists the hostnames it covers, so names leak the moment HTTPS is turned on |
| Passive DNS | Resolvers and sensors record which names resolved to which addresses over time, including names that are now gone |
| Search and recon APIs | Services that crawl and index the web expose the hostnames they have seen through an API |
Certificate Transparency is the single richest free source, and crt.sh lets you query it with a plain URL. The percent sign is a SQL wildcard, so %25.lab.0xpriv.dev asks for every name under the domain.
# pull names straight from certificate transparency, one per line curl -s 'https://crt.sh/?q=%25.lab.0xpriv.dev&output=json' \ | jq -r '.[].name_value' \ | sed 's/\*\.//g' | sort -u
Doing that by hand for every source gets old fast, which is why subfinder exists. It queries dozens of passive sources in one shot and merges the results. Give it API keys for the sources that need them and the coverage jumps.
# subfinder: all passive sources, silent, saved to a file subfinder -d lab.0xpriv.dev -all -silent -o subs_subfinder.txt
OWASP Amass covers similar ground and its passive mode is a strong second opinion, since the two tools do not always overlap. assetfinder is a tiny, fast third pass that often catches a few names the others miss.
# amass passive mode, no packets to the target amass enum -passive -d lab.0xpriv.dev -o subs_amass.txt # assetfinder, subdomains only assetfinder --subs-only lab.0xpriv.dev > subs_assetfinder.txt
$ subfinder -d lab.0xpriv.dev -all -silent | sort -u | wc -l 47 $ subfinder -d lab.0xpriv.dev -all -silent | head -4 www.lab.0xpriv.dev api.lab.0xpriv.dev staging.lab.0xpriv.dev mail.lab.0xpriv.dev
Passive tools are only as good as the sources they can reach. Adding free API keys for the common providers can double what subfinder and amass return, so set them up once in the config and forget about it.
05Active DNS brute forcing and permutations
Active brute forcing is how you find the names no public source ever recorded, by generating candidates and asking DNS whether each one resolves. You take a wordlist of common labels, prepend each to the target domain, and send the queries through fast resolvers. A hit means the name exists, a miss means it does not, and a good wordlist decides how many real hosts you catch.
Wordlist quality matters more than raw size. A curated DNS list such as the ones in SecLists hits far more often than a giant generic dictionary, and it finishes sooner. dnsx can brute force directly from a wordlist against the target.
# dnsx: brute force labels from a wordlist against the domain dnsx -silent -d lab.0xpriv.dev \ -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \ -o subs_brute.txt
For large lists, puredns is the workhorse. It drives massdns under the hood, runs at high speed, and, importantly, filters out wildcard noise. Many domains answer every possible name with the same address (a wildcard record), which would otherwise flood your results with garbage. puredns validates against trusted resolvers to strip that out.
# puredns: fast brute force with wildcard filtering puredns bruteforce subdomains-top1million-110000.txt lab.0xpriv.dev \ -r resolvers.txt --wildcard-tests 30 -w subs_puredns.txt
Permutations squeeze more out of the names you already found. If api.lab.0xpriv.dev exists, then api-dev, api2 and dev-api are worth a guess. A permutation tool like dnsgen or gotator expands your known names into variants, and you resolve the output the same way you resolve a wordlist.
# generate permutations from known names, then resolve them cat subs_known.txt | dnsgen - | puredns resolve -r resolvers.txt -w subs_perm.txt
If a domain has a wildcard DNS record, naive brute forcing reports thousands of "live" hosts that all point at one address. Always run brute force through a tool that does wildcard detection, or filter the results by checking whether a random, certainly-nonexistent name also resolves.
06Resolving candidates and probing for live hosts
Resolving and probing is the verification half, where the raw candidate pile becomes a list of hosts that actually answer. A name in a certificate or a wordlist hit is only a lead. Many will not resolve at all, and plenty that resolve will not serve anything on a web port. Two passes clean that up: resolve in DNS, then probe the survivors over HTTP and HTTPS.
First, keep only the names that resolve. dnsx reads your merged candidate file and drops anything DNS cannot answer, which can cut the list in half.
# resolve: keep only names that exist in DNS dnsx -l subs_all.txt -silent -o subs_resolved.txt
Next, probe the resolved names to see which run a web service, and capture a little context while you are at it. httpx checks both schemes, follows to the real response, and can report the status code, page title and detected technology in one pass. That context tells you which hosts are worth a closer look before you have opened a single one in a browser.
# probe: find live web hosts and grab status, title and tech httpx -l subs_resolved.txt -silent \ -status-code -title -tech-detect -o subs_live.txt
$ httpx -l subs_resolved.txt -silent -status-code -title -tech-detect https://www.lab.0xpriv.dev [200] [Acme Home] [nginx] https://api.lab.0xpriv.dev [401] [] [nginx] https://staging.lab.0xpriv.dev [200] [Staging Login] [Apache,PHP] https://grafana.lab.0xpriv.dev [302] [] [nginx]
Read that output like a to-do list. The 401 on the API says there is an auth boundary to understand. The staging login on an older stack is the soft target the flagship site is not. A 302 on a monitoring tool is worth following to see where it lands.
07Building and deduplicating an asset list
Building the asset list is the moment all the passes become one source of truth, and the key operation is deduplication. You have output from crt.sh, subfinder, amass, assetfinder, dnsx brute and permutations, and they overlap heavily. Merge them, lower-case everything, and collapse duplicates so each hostname appears exactly once.
# 0xpriv.dev/recon : merge every source into one unique, sorted list cat subs_subfinder.txt subs_amass.txt subs_assetfinder.txt \ subs_brute.txt subs_puredns.txt subs_perm.txt \ | tr 'A-Z' 'a-z' | sort -u > subs_all.txt wc -l subs_all.txt
The tool anew is handy for keeping a running list across runs, since it appends only names it has not seen before and prints the new ones, which makes repeat passes on a long engagement cheap. Once merged and resolved, the live file from httpx is your deliverable for this phase: a deduplicated set of hostnames, each confirmed to resolve and serve, with a note on what it is.
Collect passively
Run crt.sh, subfinder, amass passive and assetfinder, save each to its own file.
Collect actively
Brute force with a curated wordlist through puredns or dnsx, add permutations of what you found.
Merge and dedupe
Concatenate, lower-case, sort unique into one candidate file.
Resolve and probe
Keep resolvable names with dnsx, then probe live web hosts with httpx and record status, title and tech.
08A worked pipeline against lab.0xpriv.dev
A worked pipeline ties every step into one repeatable run, so you can point it at a scope and come back to a finished asset list. The chain below is the whole method condensed: passive collection, active brute force, merge, resolve, probe. It is deliberately readable rather than clever, because on a real engagement you want to understand every line before you run it.
# one-shot recon pipeline, lab target only DOMAIN=lab.0xpriv.dev WL=/usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt # 1. passive subfinder -d "$DOMAIN" -all -silent > subs_passive.txt amass enum -passive -d "$DOMAIN" >> subs_passive.txt assetfinder --subs-only "$DOMAIN" >> subs_passive.txt # 2. active brute force with wildcard filtering puredns bruteforce "$WL" "$DOMAIN" -r resolvers.txt -q > subs_brute.txt # 3. merge, resolve, probe cat subs_passive.txt subs_brute.txt | tr 'A-Z' 'a-z' | sort -u > subs_all.txt dnsx -l subs_all.txt -silent | httpx -silent -status-code -title -tech-detect \ | tee subs_live.txt
$ wc -l subs_all.txt subs_live.txt 63 subs_all.txt 18 subs_live.txt $ 63 candidates narrowed to 18 live web hosts, ready for the next phase
09Scope discipline
Scope discipline is the rule that keeps enumeration from quietly wandering off the engagement, and it matters more here than almost anywhere else. DNS data is messy. A root domain can point at hosts you are not allowed to touch, shared infrastructure that belongs to a provider, or names owned by a completely different company that happens to share a CDN. Finding a name does not put it in scope.
Build a filter before you build a list. Keep an explicit allow-list of the exact domains and ranges in your rules of engagement, and pass every candidate through it before you resolve or probe. If a name sits on third-party infrastructure, confirm in writing that the provider and the client both authorize testing it, or drop it. When something interesting turns up just outside scope, the move is to report it and ask, not to probe it and apologize later.
Passive collection touches nobody, but the moment you resolve or probe a name you are sending traffic to that host. Apply your scope filter before the dnsx and httpx steps, not after, so an out-of-scope host never receives a single request from you.
10Detecting enumeration against your own assets
Detecting enumeration is the defender's side of this, and the active half leaves a clear signature if you are watching DNS. Brute forcing generates a flood of queries for names that do not exist, so a spike in NXDOMAIN responses from one source, all under a single zone, is the tell. Authoritative DNS logs and resolver telemetry both show it. The passive half is invisible to you by design, since it never touches your servers, which is exactly why you cannot rely on catching recon and must assume your subdomain list is already known.
On the web side, a short burst of first-contact requests across many hostnames, each with a tool's default user agent and no referer, is a probing pass. It is low value to alert on every single one, but a sudden sweep across your whole estate from one address is worth a look, and it pairs well with the NXDOMAIN signal to confirm an active enumeration run.
11Defending your subdomain footprint
Defending the footprint is less about stopping enumeration, which you cannot, and more about making sure what gets found is small, intentional and safe to find. Since certificate transparency publishes every name the moment you issue a certificate, obscurity is not a control. Treat your public subdomain list as common knowledge and secure each host as if an attacker already has the name, because they do.
12Frequently asked questions
Is passive subdomain enumeration legal without authorization?FAQ
Querying public datasets like certificate transparency or passive DNS does not touch the target, so collecting names from them carries little risk on its own. The line is crossed when you resolve or probe those names, which sends traffic to hosts you may not be allowed to touch. Stay on the right side of it by only resolving and probing names that are inside an authorized scope.
Which is better, subfinder or amass?FAQ
Neither replaces the other. They query overlapping but different sets of sources, so running both and merging the results finds more than either alone. subfinder is fast and simple for a passive sweep, amass goes deeper and can also do active work. Use both, then deduplicate.
Why do I need to resolve names if a tool already reported them?FAQ
Passive sources record names that existed at some point, including ones that are now gone. A certificate from last year proves a name was used, not that it still resolves today. Resolving with dnsx drops the dead entries, and probing with httpx keeps only those serving a web app, so you spend time on real targets.
What makes a good DNS brute force wordlist?FAQ
Relevance beats size. A curated list built from names that really appear on the internet, such as the DNS lists in SecLists, hits far more often than a huge generic dictionary and finishes sooner. Add permutations of the names you have already discovered to catch an organization's internal naming pattern.
How do I avoid false positives from wildcard DNS?FAQ
A wildcard record answers every possible name with the same address, which makes naive brute forcing report thousands of fake hosts. Use a tool that detects wildcards, such as puredns, or test a random name that cannot exist: if it resolves, the domain is wildcarding and you must filter results against that baseline address.
13Wrapping up
Subdomain enumeration rewards patience and a clean process far more than any single clever trick. Pull the easy wins from certificate transparency, passive DNS and the search APIs with subfinder, amass and assetfinder, then go after the hidden names with a curated wordlist and permutations through puredns or dnsx. Resolve what you find, probe what resolves, and merge everything into one deduplicated asset list before you touch the next phase. Keep the whole run inside an authorized scope, and remember the defender lesson that falls out of all of this: your subdomains are public the day you issue a certificate, so secure every host as if the name is already known.
references
Founder of 0xpriv, covering offensive security, web application vulnerabilities, and practical security research through PHP, Python, and hands-on technical analysis.