dnsenum
dnsenum runs a full DNS pass against one domain in a single command: it pulls the A, NS, and MX records, tries a zone transfer against every name server, brute forces subdomains from a wordlist, and can sweep the netranges it finds. Reach for it in the recon phase when the first question is “what does this domain actually expose?” and you would rather not type six separate dig commands. Only enumerate domains you own or are explicitly authorized to test.
Mental model: dnsenum [options] DOMAIN. The bare domain is the target; every capability is one flag away.
New to DNS? Core concepts
- A / AAAA record: the IPv4 / IPv6 address of a host; dnsenum lists these as “Host’s addresses”
- NS record: the authoritative name servers for the zone; dnsenum tries an AXFR against each one
- MX record: the mail exchangers for the domain, useful for in-scope mail-path testing
- PTR record: the reverse mapping from IP back to a name, produced by the reverse/netrange sweep
- Zone transfer (AXFR): asking a name server for the entire zone at once,
dig AXFR DOMAIN @NS - Resolver: the DNS server you query (
--dnsserver); different resolvers can answer differently in split-horizon setups - Subdomain brute force: guessing names from a wordlist (
-f); it only finds names that are in the list - Wordlist: the candidate name list you feed to
-f(SecLists is the usual source)
When to reach for dnsenum (and when not)
Reach for it to get records, name servers, mail exchangers, and a zone-transfer check on a single in-scope domain in one command, to confirm your own zone does not leak its contents via AXFR, or as an early recon pass that maps the DNS surface before deeper testing.
Reach for something else when you only need one record type (dig DOMAIN MX +short is faster and cleaner), when you need fast or large-scale subdomain brute forcing (gobuster dns mode, ffuf, amass), when passive or certificate-log discovery would do (amass, subfinder), or when you want structured JSON/CSV output and current maintenance (dnsrecon).
Install, update, verify#
sudo apt install -y dnsenum # Debian / Ubuntu / Kali
sudo apt install --only-upgrade -y dnsenum # update
# No distro package? clone dnsenum2 and install its Perl modules
git clone https://github.com/SparrowOchon/dnsenum2.git && cd dnsenum2
sudo cpan Net::IP Net::DNS Net::Netmask String::Random \
XML::Writer Net::Whois::IP HTML::Parser WWW::Mechanize
command -v dnsenum # confirm it is on PATH
dnsenum --help # version banner + option groups (the confirmation)
dnsenum is a Perl script, so Perl plus those modules are the only hard requirement; the apt package pulls them in for you. On the git checkout there is nothing on PATH, so run it as perl dnsenum.pl. There is no self-test: a clean --help listing is your “it works”.
Flags you’ll actually use#
Skim these first; the cheat sheet below is these flags combined.
| Flag | Does | Flag | Does |
|---|---|---|---|
--enum | Shortcut for --threads 5 -s 15 -w | -o | Write XML output (overwrites each run) |
--dnsserver | Resolver to query | -s | Max names scraped from search |
-f / --file | Subdomain wordlist to brute | -p | Search pages to scrape |
--threads | Parallel query workers | -w / --whois | whois netranges, then PTR-sweep them |
--noreverse | Skip the reverse/netrange sweep | -r | Recurse into found subdomains |
-t / --timeout | Query timeout in seconds | --subfile | Write valid subdomains to a file |
-u | Append valid subs into the -f file | --private | Keep private IPs (DOMAIN_ips.txt) |
-e / --exclude | Drop PTR names matching a regexp | -v | Verbose progress and errors |
Cheat sheet#
Every block starts simple and adds one flag at a time, so you can stop at the line that does the job.
# Build up a run: stop at the line you need
dnsenum DOMAIN # 1. records + NS + MX + AXFR + reverse sweep
dnsenum --noreverse DOMAIN # 2. + drop the slow reverse/netrange sweep
dnsenum --noreverse -o scans/d.xml DOMAIN # 3. + archive XML for your notes
dnsenum --dnsserver RESOLVER --noreverse -o scans/d.xml DOMAIN # 4. + a chosen resolver
# Record + zone-transfer triage (no brute force)
dnsenum --noreverse DOMAIN # A/AAAA, NS, MX, one AXFR try per name server
dnsenum --enum DOMAIN # default profile: threads 5, scrape 15, whois
dnsenum --noreverse -s 0 -p 0 DOMAIN # records only, no search-engine scraping
# Choose the resolver
dnsenum --dnsserver RESOLVER DOMAIN # ask the box's own name server (split-horizon)
dnsenum --dnsserver 1.1.1.1 DOMAIN # ask a known-good public resolver
# Subdomain brute force (only when in scope)
dnsenum -f WORDLIST DOMAIN # brute names from a wordlist
dnsenum -f WORDLIST --threads 10 DOMAIN # + more parallel workers
dnsenum -f WORDLIST -r DOMAIN # + recurse into found subdomains
dnsenum -f WORDLIST --subfile hosts.txt DOMAIN # + write valid subs to their own file
dnsenum -f WORDLIST -u a -o out.xml DOMAIN # + append valid names back into WORDLIST
# Zone transfer
dnsenum --noreverse DOMAIN | tee scans/axfr.txt # dnsenum's AXFR attempt, saved
dig AXFR DOMAIN @RESOLVER # confirm it directly (the proof)
# Reverse / netrange (the slow, loud part)
dnsenum -w DOMAIN # whois netranges, then PTR-sweep them
dnsenum -w --private DOMAIN # also keep private IPs (DOMAIN_ips.txt)
dnsenum -e '^unused' -w DOMAIN # drop junk PTR names by regexp
# Save and tune
dnsenum -o "scans/dnsenum-$(date +%F-%H%M).xml" DOMAIN # timestamped XML, no overwrite
dnsenum -t 5 DOMAIN # shorter timeout on a slow link
dnsenum -o scans/d.xml DOMAIN 2>&1 | tee notes/d.txt # XML + readable transcript
Command breakdowns#
Each block below explains one situation, the exact command, and what to expect back.
Enumerate a domain fast (records, NS, MX, AXFR)#
dnsenum --noreverse DOMAIN -o scans/quick.xml
Returns the host’s A/AAAA addresses, the NS records, the MX records, and one zone-transfer attempt per name server (almost always refused). --noreverse skips the slow PTR/netrange sweep, so this is the triage view you run first.
Run the sensible default profile#
dnsenum --enum DOMAIN -o scans/enum.xml
--enum expands to --threads 5 -s 15 -w: the same record sections plus a little search scraping and a whois netrange pass. Good all-round default, but note the -w means it will try the slower netrange work.
Query a specific resolver (split-horizon / internal DNS)#
dnsenum --dnsserver RESOLVER --noreverse DOMAIN -o scans/internal.xml
Sends every query to the resolver you name instead of the system default. Point it at the box’s own name server when an internal zone answers differently from the public internet.
Test for a zone transfer and confirm it#
dnsenum --noreverse DOMAIN | tee scans/axfr.txt
dig AXFR DOMAIN @RESOLVER
dnsenum tries an AXFR against each NS and prints the result. “Transfer failed” is the normal outcome; a full zone dump is a real finding, so always reproduce it with dig AXFR before you trust or report it.
Brute force subdomains from a wordlist#
dnsenum --dnsserver RESOLVER -f WORDLIST DOMAIN -o scans/brute.xml
Adds a “Brute forcing” section that lists only the wordlist names that actually resolve. It finds nothing that is not in WORDLIST, so list choice is everything. Only run this when brute forcing is in scope.
Speed up the brute force with more threads#
dnsenum -f WORDLIST --threads 10 DOMAIN -o scans/brute.xml
--threads sets how many queries run in parallel. More threads finish a big list faster but hammer the resolver; back off on a fragile lab name server that starts dropping answers.
Recurse into discovered subdomains#
dnsenum -r -f WORDLIST --threads 10 DOMAIN -o scans/recursive.xml
-r re-runs the brute force against each subdomain that has its own NS record, so you catch names one level deeper. It multiplies the query count, so keep the wordlist and scope tight.
Save discovered subdomains to their own file#
dnsenum -f WORDLIST --subfile scans/subs.txt DOMAIN -o scans/brute.xml
--subfile writes just the valid subdomains to a clean file while -o keeps the full XML. Handy when you want a plain host list to feed straight into the next tool.
Grow your wordlist from valid hits#
dnsenum -f WORDLIST -u a DOMAIN -o scans/brute.xml
-u a appends every validated name (from all sources) back into the -f wordlist, so the next run starts warmer. The letters a|g|r|z pick which sources feed the update (all, scraping, reverse, zone transfer).
Sweep netranges with whois and reverse lookups#
dnsenum -w DOMAIN -o scans/netrange.xml
-w runs whois on the found addresses to derive class-C netranges, then PTR-scans them for extra hostnames. It is the richest pass and also the slowest and loudest; use it only when you actually want reverse data.
Skip the slow reverse sweep#
dnsenum --noreverse DOMAIN -o scans/records.xml
--noreverse turns off the reverse/netrange work entirely. Reach for it whenever you only need records, NS, MX, and the AXFR check and do not want to wait on PTR scanning.
Save XML output for notes and tooling#
dnsenum -o "scans/dnsenum-$(date +%F-%H%M).xml" DOMAIN 2>&1 | tee notes/dnsenum.txt
-o writes MagicTree-importable XML but overwrites the file each run, so timestamp the name. Piping through tee keeps a readable transcript alongside the structured file.
Pull a clean host list from the output#
grep -Eo '[a-z0-9.-]+\.DOMAIN' scans/brute.xml | sort -u | tee notes/hosts.txt
Extracts every hostname under the target domain from the XML and dedupes it, so the useful names are not buried in the transcript. This list feeds nmap, gobuster, or ffuf next.
Cut noise on a fragile lab resolver#
dnsenum --dnsserver RESOLVER --noreverse -t 5 --threads 3 DOMAIN
Lower --threads and a short -t timeout keep a small or overloaded resolver from dropping queries and handing you false negatives. Slower, but the answers you get are trustworthy.
Reading the output#
| Output section | What it is | Do next |
|---|---|---|
| Host’s addresses | A/AAAA of the bare domain | First targets; feed the IPs to nmap |
| Name Servers (NS) | The authoritative servers, each gets an AXFR try | Read the AXFR line under each |
| Mail (MX) Servers | The mail exchangers | Mail-path checks if in scope |
| Trying Zone Transfer | The AXFR attempt result | “failed” is normal; a full dump is a finding, confirm with dig |
| Brute forcing | Wordlist names that resolved | Add to targets; no hits is not “no subdomains” |
| network ranges / reverse | whois netranges plus PTR names | Extra hosts, but this part is slow and loud |
No brute-force hits means only that none of your wordlist names resolved, not that the domain has no subdomains. Different resolvers returning different answers is normal in split-horizon DNS, so note which resolver produced which result.
Troubleshooting#
| Problem | Cause | Fix |
|---|---|---|
command not found | Not installed / stale PATH | Reinstall, or run perl dnsenum.pl from the git copy |
Can't locate Net/DNS.pm | Missing Perl module | sudo cpan Net::DNS (and any other named module) |
| Run hangs forever | Resolver unreachable or filtering you | Set --dnsserver RESOLVER; lower -t timeout |
| No records returned | Domain does not resolve | Verify the name, try dig DOMAIN @RESOLVER |
| Zone transfer always fails | Server refuses AXFR (normal) | Expected; only a full dump is a finding |
| Brute force finds nothing | Wordlist too small or wrong | Use a larger in-scope WORDLIST |
| Reverse sweep takes forever | whois netrange PTR scan is slow | Add --noreverse |
| Flags differ from a tutorial | dnsenum vs dnsenum2 versions | Check dnsenum --help; Kali ships dnsenum2 |
Gotchas#
- Search-engine scraping is mostly dead.
-s/-prarely returns names anymore, so lean on-fwith a real wordlist and treat any scraped name as a bonus. --enumquietly turns on whois (-w). It is not just “fast defaults”: it can kick off a slow netrange and reverse sweep, so pair it with--noreversewhen you only want records.- A dnsenum AXFR “success” can lie. Reproduce every zone dump with
dig AXFR DOMAIN @RESOLVERbefore you trust or report it. - Threads hit the resolver, not the domain. High
--threadshammers whatever--dnsserveryou set; a fragile lab resolver silently drops queries and you read the loss as “no subdomains”. -ooverwrites. The XML path is truncated on every run, so timestamp the filename or you lose the last scan.- Brute forcing is loud. A wordlist run is a burst of NXDOMAIN queries and the AXFR tries are logged as AXFR requests, both textbook enumeration signatures a defender alerts on.
Pairs with#
dig and host confirm any single finding, and dig AXFR DOMAIN @RESOLVER is the proof a zone transfer really works. Feed the A-record IPs straight into nmap for port scanning. For large or fast subdomain lists, gobuster (dns mode) and ffuf brute force far quicker than dnsenum’s Perl loop, while amass and subfinder add passive and certificate-log discovery dnsenum never sees. Reach for dnsrecon when you want the same scripted pass with JSON/CSV output and more active maintenance.