Amass
Amass maps the external DNS footprint of a domain: it pulls hostnames from dozens of OSINT sources, resolves and validates them through DNS, optionally probes for live names, and stores everything in a local graph database so you can compare runs over time. Reach for it when a single subdomain list is not enough and recon needs to be repeatable: an authorized external assessment, bug-bounty scope mapping, or a CTF box that hands you a domain instead of an IP.
Mental model: amass [subcommand] [mode] -d DOMAIN [options]. The subcommand picks the job, the mode picks how loud.
New to subdomain recon? Core concepts
- Subcommand:
enumfinds names,intelfinds domains and org context,dbreads stored results,vizexports a graph - Passive vs active:
-passiveonly queries OSINT sources;-activeadds direct techniques (zone transfer, cert grabbing) that touch the target - Brute forcing:
-brute -w WORDLISTguesses names straight against DNS, the loudest mode - Resolution: a name in a dataset is not proof it resolves; Amass resolves candidates to IPs, and
-ipshows them - Scope:
-d DOMAIN(one) and-df domains.txt(a list) fence the run to what you are allowed to touch - Config:
-configloads API keys, source toggles, and resolver lists, which is what makes passive coverage actually good - Graph database: results persist under
~/.config/amass/(or-dir), so a later run can diff against an earlier one - Wildcard DNS: a zone that answers every name makes junk “resolve”, so stay suspicious of uniform results
When to reach for Amass (and when not)
Reach for it to map the full attack surface of an authorized domain before scanning, discover in-scope subdomains and forgotten assets across a large bug-bounty program, expand a CTF domain into hostnames and vhosts, inventory shadow IT on your own domains, or diff the stored database to spot names that appeared since the last run.
Reach for something else when you only have an IP and no domain (Amass is name-centric, so start with nmap), when you want one quick passive name list fast (subfinder, theHarvester), or when you need to test the services rather than find them (Amass finds names, it is not a vulnerability scanner).
Install, update, verify#
sudo apt install -y amass # Debian / Ubuntu / Kali (often older)
go install -v github.com/owasp-amass/amass/v3/...@latest # amass v3 (this guide's enum/intel/db/viz workflow)
sudo snap install amass # cross-distro, self-updating
sudo apt install --only-upgrade -y amass # update (distro)
go install -v github.com/owasp-amass/amass/v3/...@latest # update (Go: re-run)
sudo snap refresh amass # update (snap)
amass -version # v3 and v4 differ in flags and db location, so know which you run
command -v amass # confirm it is on PATH (Go puts the binary in ~/go/bin)
amass -help # list subcommands: enum, intel, db, viz
This guide covers amass v3 (the enum/intel/db/viz workflow). v4 and v5 removed the db and viz subcommands and the enum -ip/-src/-json flags, so pin v3 with the Go install above if apt or snap give you a newer build - check amass -version. For a throwaway environment use docker run -v "$PWD":/data caffix/amass enum -passive -d DOMAIN -o /data/names.txt. If command -v prints nothing after go install, add ~/go/bin to your PATH and open a fresh shell.
Flags you’ll actually use#
Skim these first; the cheat sheet below is these flags combined.
| Flag | Does | Flag | Does |
|---|---|---|---|
enum | Subcommand: find subdomains | -config | Load API keys, sources, resolvers |
intel | Subcommand: find domains / org context | -o | Save a flat name list |
db | Read the stored graph database | -json | Structured JSON output |
-passive | OSINT sources only, no probing | -ip | Show resolved IPs next to names |
-active | Add zone transfer + cert grabbing | -src | Show which source found each name |
-brute | DNS brute force (pair with -w) | -dir | Custom database / output dir |
-d | In-scope domain (comma-separates) | -rf | Resolvers file (custom DNS) |
-df | File of in-scope domains | -v | Verbose progress and timeouts |
Cheat sheet#
Each block starts simple and adds one flag at a time, so you can stop at the line that does the job.
# Build up an enumeration: stop at the line that fits your scope
amass enum -d DOMAIN # 1. default enum (passive + light active)
amass enum -passive -d DOMAIN # 2. OSINT only, nothing touches the target
amass enum -passive -config CONFIG -d DOMAIN # 3. + API keys = far better coverage
amass enum -passive -config CONFIG -d DOMAIN -o enum/names.txt # 4. + save the list
# Active recon (scope permitting, this touches target infrastructure)
amass enum -active -d DOMAIN # zone transfer attempts + TLS cert names
amass enum -active -ip -d DOMAIN # + show resolved IPs inline
amass enum -brute -w WORDLIST -d DOMAIN # DNS brute force (loudest, authorized only)
# Scope: one domain, several, or a file
amass enum -passive -d DOMAIN # single in-scope domain
amass enum -passive -d "a.com,b.com" # a couple, comma-separated
amass enum -passive -df domains.txt # a file, one in-scope domain per line
# Output control
amass enum -passive -d DOMAIN -o enum/names.txt # flat name list
amass enum -passive -d DOMAIN -json enum/out.json # structured JSON
amass enum -passive -d DOMAIN -src # tag each name with its source
jq -r '.name' enum/out.json | sort -u # unique names out of the JSON
# Tune sources, resolvers, runtime
amass enum -passive -d DOMAIN -list # list available data sources
amass enum -active -d DOMAIN -rf resolvers.txt # use your own resolver list
amass enum -active -d DOMAIN -timeout 10 # stop after 10 minutes
# Intel: find domains before you enumerate names
amass intel -d DOMAIN -whois # related domains via reverse WHOIS
amass intel -org "Example Lab Org" # domains / ASNs tied to an org name
amass intel -asn 64500 # domains seen in an ASN
amass intel -cidr 192.168.0.0/24 # domains seen in a CIDR block
# Stored database (results persist, so runs can be compared)
amass db -dir ./db -list # list past enumerations in the db
amass db -names -d DOMAIN # every name recorded for the domain
amass db -show -d DOMAIN # full stored detail (sources, addresses)
# Visualize
amass viz -d3 -d DOMAIN -dir ./db # write an interactive HTML graph
# Validate before you scan
dig +short HOSTNAME # confirm a candidate actually resolves
Command breakdowns#
Each block below explains one situation, the exact command, and what to expect back.
Run a safe first pass (passive only)#
amass enum -passive -d DOMAIN -o enum/passive.txt
Queries public datasets only and never touches the target. Success is a populated enum/passive.txt, one hostname per line. Treat every name as a lead, not a confirmed host.
Pull far more names with API keys#
amass enum -passive -config /path/config.ini -d DOMAIN -o enum/passive-keys.txt
-config loads free-tier source keys, and passive coverage jumps once they are set. Thin output almost always means no keys, a tiny footprint, or a typo in the domain, not that the target is empty.
Enumerate several in-scope domains at once#
amass enum -passive -df domains.txt -o enum/multi.txt
-df reads one in-scope domain per line; -d "a.com,b.com" does the same inline for a couple. Everything you feed it is treated as in scope, so keep the list tight.
Add active techniques when scope allows#
amass enum -active -d DOMAIN -o enum/active.txt
-active adds zone-transfer attempts and TLS certificate name grabbing on top of the passive sources. This sends real traffic to target infrastructure, so only run it when the engagement explicitly allows direct interaction.
Show resolved IPs to feed the next tool#
amass enum -active -ip -d DOMAIN -o enum/active-ip.txt
-ip prints the address next to each name, giving you name and IP pairs you can hand straight to Nmap. IPs are stronger evidence than a bare name, but still confirm one with dig.
Brute force names when sources run dry#
amass enum -brute -w WORDLIST -d DOMAIN -o enum/brute.txt
-brute guesses names directly against DNS; -w supplies your wordlist (a built-in list is used if you omit it). This is the loudest mode and produces a burst of failed lookups, so authorized use only.
Save JSON and clean it with jq#
amass enum -passive -d DOMAIN -json enum/out.json
jq -r '.name' enum/out.json | sort -u > notes/names.txt
-json writes one structured record per name (source, addresses, tag). Parse it later instead of re-running a long enumeration because the terminal scrolled away.
See which source found each name#
amass enum -passive -d DOMAIN -src
-src prefixes every name with the data source that reported it. Handy for judging whether a result came from a trustworthy dataset or a noisy one before you act on it.
Find related domains before enumerating names#
amass intel -d DOMAIN -whois
amass intel -org "Example Lab Org"
intel discovers domains rather than hostnames: -whois pivots on reverse WHOIS, -org searches registry and ASN data for an organization name. Confirm each candidate against your written scope before adding it.
Map domains across an ASN or CIDR#
amass intel -asn 64500
amass intel -cidr 192.168.0.0/24
Given a network block you own, intel lists the domains it has seen there. Use it to surface forgotten assets and shadow IT tied to your own address space.
Read what the database already knows#
amass db -names -d DOMAIN
amass db -show -d DOMAIN
Amass stores every run locally, so db -names replays the accumulated name list without re-enumerating, and -show dumps the richer detail (sources, addresses). Remember this can include names from earlier sessions, not just the last one.
List and compare past enumerations#
amass db -dir ./db -list
-list numbers every enumeration stored in the database with its domains and timestamp. This is how you tell which names are new since the last run instead of eyeballing two text files.
Keep each engagement in its own database#
amass enum -passive -d DOMAIN -dir ./db -o enum/passive.txt
amass db -dir ./db -names -d DOMAIN
-dir pins the graph database and output to a folder you control, so targets never mix. Pass the same -dir to db and viz to read that engagement back later.
Use your own resolvers and cap the runtime#
amass enum -active -d DOMAIN -rf resolvers.txt -timeout 10
-rf points at a file of DNS resolvers so you are not hammering one server, and -timeout stops the run after N minutes. Both keep a long active enumeration from running away from you.
Visualize the graph#
amass viz -d3 -d DOMAIN -dir ./db
viz -d3 writes a self-contained HTML graph of names, addresses, and their links from the stored database. Useful for showing relationships in a report rather than reading a flat list.
Validate a name before you scan it#
dig +short candidate.DOMAIN
A name in the list is a claim; dig is the proof. Confirm it resolves, and is not a wildcard catch-all, before you point Nmap or a fuzzer at it.
Reading the output#
| You see | Means | Do next |
|---|---|---|
| A bare hostname | A source claims it exists | Validate with dig before trusting it |
name 1.2.3.4 (with -ip) | Amass resolved it to an address | Stronger, but confirm; feed the IP to Nmap |
| Every random subdomain resolves | Wildcard DNS on the zone | Discard the uniform hits; test one by hand |
| Thin or empty passive output | No API keys, small footprint, or typo | Add -config keys; recheck the domain |
Bursts of source timeouts (-v) | A few are normal, a flood is not | Check outbound DNS/HTTPS; set -rf resolvers |
| Names outside your scope | Shared hosting, CDNs, third parties | Filter against written scope before acting |
Troubleshooting#
| Problem | Cause | Fix |
|---|---|---|
command not found | Not installed / stale PATH | Reinstall; add ~/go/bin to PATH; new shell |
| Very few passive results | No API keys configured | Add free-tier keys via -config |
| Flag rejected as unknown | v3 vs v4 syntax difference | Check amass -version and current -help |
| Flood of source timeouts | Network or DNS problem | Verify outbound DNS/HTTPS; set -rf resolvers |
| Every subdomain resolves | Wildcard DNS on the zone | Validate with dig; discard uniform hits |
| No output file written | Missing -o / -json | Add an output flag and re-run |
| Database looks empty | Wrong -dir or a fresh db | Point -dir at the right folder; check defaults |
| Active mode finds nothing new | Scope blocks it / firewalled | Confirm scope; passive may be all that reaches |
Gotchas#
- A name is a claim, not a host. A source listing a subdomain does not mean it resolves, is live, or is in scope. Nothing downstream should run until
digconfirms it. - Passive is not zero-footprint. The target sees nothing, but every OSINT source and configured API sees your lookups. “Passive” means quiet to the target, not anonymous.
-activeand-bruteshow up in different logs. Active trips DNS server logs withAXFRattempts; brute forcing floods them withNXDOMAINfor names that do not exist. Both are easy to alert on.- No keys, no coverage. Out of the box Amass queries only free sources; the deep passive results everyone talks about come from
-configwith API keys set. Judge a thin run by whether keys were loaded. - Do not mix v3 and v4 syntax. Flags, subcommands, and the database path changed between major versions, so a command copied from an old tutorial can fail or land data in the wrong place.
- Keep the
-configfile out of shared repos. It holds your source API keys; committing it leaks those accounts.
Pairs with#
dig and host validate that a discovered name actually resolves before you trust it. subfinder and theHarvester are faster single-pass alternatives worth cross-checking Amass against when you just want a quick list. Once names resolve, httpx or whatweb show which are live web hosts, nmap scans the confirmed IPs, and gobuster or ffuf take directory and vhost discovery from there. jq, sort, and grep clean the name and JSON output into tidy notes.