Subdomain Enumeration: How I Map a Target's Full Attack Surface

Vertical and horizontal subdomain recon, end to end: the passive sources, the active brute forcing, why different tools find different results, and why a root domain beats an IP for mapping what a company owns.

Recon is the part of a test that decides everything after it. You can only attack what you can see, and on a web target the first thing I want to see is the full list of hostnames a company actually runs. Not the one site they pointed me at, all of them. This is subdomain enumeration, and it is where I start on almost every external engagement. Let me walk through how I do it properly, in both directions, and answer a question I get a lot: why do people hand a hacker a domain name instead of an IP address?

What am I actually looking for?

A company's public footprint is much bigger than its homepage. Behind one brand there are usually dozens or hundreds of hostnames: api, dev, mail, vpn, jira, and that staging box someone spun up in 2019 and forgot. The main site is hardened because everyone looks at it. The forgotten ones are where the bugs live: the old app with no WAF in front of it, the dev copy with debug mode left on, the admin panel that was never meant to face the internet. So the goal is simple to state and slow to do well: build the most complete list of live hosts this org owns, then work out which ones are soft.

Vertical and horizontal: the two directions

There are two ways to grow that list, and a real recon run uses both.

  • Vertical enumeration goes down one domain. You start with example.com and find everything under it: api.example.com, dev.example.com, internal.api.example.com. Deeper into one tree.
  • Horizontal enumeration goes sideways across the org. You start with example.com and find the other root domains the same company owns: example.io, an example-cdn.net, the startup they acquired last year, the marketing microsite someone launched on a different TLD. Wider across the whole business.

You need both, because the domain you were given is almost never the whole company. Horizontal turns up roots you did not know existed, and vertical then digs into each one. The two feed each other in a loop, and skipping either one leaves assets on the table.

Why a root domain and not an IP?

Here is the question I get from people new to this. If you want to find a company's assets, why start from a fuzzy-sounding name like example.com instead of a precise IP address? In practice it is the other way around. The name is the precise thing and the IP is the dead end. Here is why.

  • One IP hosts many sites. Shared and virtual hosting means a single IP can serve hundreds of unrelated websites. The server decides which site to show based on the Host header in your request, not the IP itself. So an IP does not map to one company, it maps to a machine that might hold a thousand strangers.
  • The IP usually belongs to a provider, not the target. Most companies run on AWS, Google Cloud, Azure, or behind Cloudflare. The public IP you see belongs to Amazon or Cloudflare and is shared across huge numbers of tenants. It rotates, it autoscales, and it tells you nothing about who the real owner is.
  • CDNs and WAFs hide the real origin. When a site sits behind Cloudflare, the IP that answers is Cloudflare's, shared by millions of sites. The company's actual server is somewhere behind it. So the IP points at the shield, not the target. Finding the hidden origin is its own game, and I covered one way it leaks in the Cloudflare tunnel writeup.
  • Reverse DNS is unreliable. You would hope to take an IP, do a reverse lookup (PTR), and get the hostname back. Most IPs have no useful PTR record, or one that just names the hosting provider. The data is sparse and usually meaningless.
  • Scope is defined by the name. A company owns its domain names. It does not always own the IP block its sites sit on, that belongs to the cloud provider. So bug bounty and pentest scope is written as *.example.com, not as an IP range, because the name is what the org actually controls and can authorize you to test.

The domain, by contrast, is an identity. It ties cleanly to an owner through registration records, it appears in every TLS certificate the company ever issues, and it is the key that the public datasets are indexed by. Give a good recon toolkit a name and the whole discovery machinery unfolds from it. Give it a shared cloud IP and it mostly shrugs.

The one time IPs genuinely pay off is when a large org owns its own IP space, an allocated block with its own ASN. Then you can pivot the other way: name to ASN to IP ranges to reverse DNS to more names. That is a horizontal technique I will get to below. Notice that even then, you start from the name to find the ASN.

Vertical enumeration: passive first

I always start passive, which means collecting hostnames from other people's data without sending anything at the target. It is quiet, and it is where most of the results come from. The main sources:

  • Certificate Transparency logs. Every time someone issues a TLS certificate, it gets written to a public, append-only log, including all the hostnames on that cert. This is the single richest source. If a company got a certificate for secret-admin.example.com, that name is sitting in a public log forever. crt.sh and certspotter search these.
    curl -s 'https://crt.sh/?q=%25.example.com&output=json' \
      | jq -r '.[].name_value' | sort -u
  • Passive DNS and OSINT datasets. Services like SecurityTrails, VirusTotal, Shodan, Censys, and Netlas keep historical records of what resolved to what. They have seen names that stopped resolving years ago.
  • Web archives. The Wayback Machine and friends have crawled the company's pages for years, and the URLs in that history mention hostnames. Tools like gau and waybackurls pull them out.
  • Code and search. GitHub, GitLab, and plain search engines leak internal hostnames constantly. github-subdomains scrapes public code, and search dorks find the rest.

The tools most people run for this, subfinder, amass, assetfinder, findomain, are mostly aggregators. They query a set of these sources and merge the answers. Which is exactly the point of the next section.

Why do different tools give different results?

This trips people up. They run one tool, get a list, and think they are done. But no single tool sees everything, because each one queries a different set of sources.

  • subfinder queries one set of passive APIs.
  • amass queries an overlapping but different set, and can also go active.
  • assetfinder pulls from yet another handful.
  • crt.sh sees certificate logs but not passive DNS history.
  • A brute force (next section) finds names that are in no public dataset at all, because they never got a certificate and never resolved publicly.

So coverage is the union of the sources, not any single tool. The real workflow is to run several, combine the output, sort, and dedupe. And API keys matter more than people expect: subfinder and amass loaded with keys for SecurityTrails, Censys, Shodan, VirusTotal and the rest will find far more than the same tools with no keys, because you are unlocking more sources. The same tool run with keys and without can differ by hundreds of hosts.

Vertical enumeration: going active

Passive gives you what the world already knows. Active is how you find the names nobody has recorded. Now you are querying DNS directly.

  • DNS brute force. Take a big wordlist of common subdomain names and resolve each one against the domain. If dev, staging, api, vpn, and gitlab exist, they answer. This needs speed, so I drive a mass resolver like massdns through puredns or shuffledns, against a large list of trusted resolvers.
    puredns bruteforce best-dns-wordlist.txt example.com \
      -r resolvers.txt -w bruted.txt
  • Permutations. Take the subdomains you already found and generate variations: api becomes api-dev, api2, api-staging, dev-api. Tools like dnsgen, gotator, and ripgen build these, and then you resolve them. This catches the naming habits a company uses.
  • Zone transfer. Rare, but a free win. If a nameserver is misconfigured to allow it, one request dumps every record for the domain.
    dig axfr @ns1.example.com example.com
  • Certificate scraping on their hosts. Tools like cero and tlsx connect to a host and read the names straight off its TLS certificate, which often lists siblings you had not seen.

One trap here is wildcard DNS. Some domains resolve every possible name to the same place, so a brute force returns thousands of fake hits. puredns and shuffledns detect and filter wildcards for you, which is why I use them instead of resolving a wordlist by hand.

Resolve and probe: turning names into targets

A list of names is not yet a list of targets. Two more steps turn it into one:

  • Resolve. Run everything through dnsx to keep only the names that actually resolve, and to pull their records.
  • Probe. Run the resolving names through httpx to see which are live web servers, and grab the status code, page title, server, detected technology, and whether they sit behind a CDN. Now the raw list is a ranked set of live apps I can actually go after.
cat all-subs.txt | dnsx -silent | httpx -title -tech-detect -status-code -silent

Horizontal enumeration: finding the other roots

Everything above digs into one domain. Horizontal finds the domains you were never told about. The main techniques:

  • WHOIS and reverse WHOIS. Registration records list a registrant organisation or email. Reverse WHOIS asks the opposite question: what other domains share this registrant? amass intel and Whoxy do this. The catch is privacy, since a lot of WHOIS data is redacted now, so it works better on older or corporate registrations.
  • ASN and IP ranges. If the company is big enough to own its own IP space, it has an ASN. Map it with asnmap, amass intel, or bgp.he.net, pull their netblocks, then reverse-DNS those ranges to shake out more hostnames and domains. This is the case where IPs finally pay off, and you still started from the name to find the ASN.
  • Favicon hashing. Hash the little icon in the browser tab and search Shodan or Censys for every other host serving the identical favicon. Companies reuse one favicon across their properties, so this surfaces siblings quickly. fav-up automates it.
  • Certificate pivoting. Certificates carry an organisation field and multiple names. Searching Censys or crt.sh by the org name links domains that share a certificate or an issuer identity.
  • Content fingerprints. The same Google Analytics ID, Tag Manager ID, unique copyright line, or S3 bucket naming across sites ties them to one owner. publicwww and BuiltWith let you search the web for a shared string or tracker.
  • Acquisitions and brands. Some of it is plain OSINT. The company's Crunchbase, Wikipedia, and press releases name subsidiaries and acquisitions, and each of those is its own root domain to go and enumerate.

Every new root domain this turns up goes straight back into vertical enumeration. Horizontal widens, vertical deepens, and you loop until the list stops growing.

The toolbox

A rough map of what I reach for. None of it is magic on its own. The skill is running the right combination and unioning the output.

  • Passive vertical: subfinder, amass (passive), assetfinder, findomain, crt.sh, chaos, github-subdomains, gau, waybackurls.
  • Active vertical: puredns or shuffledns with massdns, dnsgen / gotator / ripgen for permutations, cero / tlsx for certificate names, dig for zone transfers.
  • Resolve and probe: dnsx, httpx, tlsx.
  • Horizontal: amass intel, asnmap, Whoxy reverse WHOIS, fav-up, Shodan, Censys, Netlas.
  • All-in-one: bbot chains most of this into a single pipeline.
  • Wordlists: SecLists, and the Assetnote best-dns wordlist for brute forcing.

And load your API keys. Half the value of subfinder and amass is locked behind keys for the paid data sources. Without them you leave most of the passive results sitting there.

What does recon actually pay off into?

All of this is setup for the real testing, but a couple of findings fall straight out of good recon on their own. The obvious one is a forgotten host: a staging or admin box that was never hardened because nobody remembered it was public. The other is subdomain takeover, where a subdomain still has a DNS record pointing at a cloud service (an S3 bucket, a Heroku app, a Netlify site) that the company already deleted. Because the record is left dangling, anyone can go and claim that service and serve their own content from the company's subdomain. You only find those by enumerating thoroughly and checking every name, which is the whole reason coverage matters so much.

The defender's side

Flip all of this around and it is attack surface management. Everything I run against a target, a security team should be running against itself, on a schedule. Attackers enumerate you whether you like it or not. The only real question is whether you have seen your own forgotten subdomains before they do. The dangling DNS record, the staging box with debug on, the acquisition nobody re-secured, these are all found by the same recon, and the side that runs it first wins.

Recon is unglamorous and it quietly decides everything downstream. A test that starts from a thin asset list finds thin bugs. If you want your real external attack surface mapped the way an attacker would map it, root domains, siblings, forgotten hosts and all, that is where I start.