Labs: DNS enumeration without touching real infrastructure
These exercises teach subdomain enumeration on fictional zones. -simulate-zone
loads a scenario file and answers every query from a DNS server that subenum
runs on 127.0.0.1 for the length of the scan. No DNS traffic leaves your
machine, so labs are safe in a classroom, a CTF or an air-gapped VM.
Unlike -simulate, which invents results from a hit rate, a lab runs the real
resolver against the scenario. Wildcard detection, -rate pacing, retries, the
reliability guard and the run-quality report all behave exactly as they would
against a live zone, and the results are the same on every run.
Setup
You need a subenum binary (see Installation)
and a copy of the repository for the scenario files. For a zero-install
classroom, open the labs in GitHub Codespaces:
the container builds ./subenum, opens this page and the first scenario, and
prints the lab 1 command.
Every lab scans lab.example with the short wordlist examples/labs/words.txt.
Run the commands from the repository root:
L=examples/labs
W="-w $L/words.txt"
Each lab ends with questions. The answers are folded underneath; try first.
Lab 1: a first scan
subenum $W -simulate-zone $L/lab1-first-scan.zone -show-records lab.example
-show-records prints the records behind each name.
- How many subdomains did the scan find?
- Which host has an IPv6 address?
shophas the same address aswww. Why?- The scenario file defines
blog, but it is not in the results. Rerun with-type A,AAAA,CNAME. What isblog, and why did the default scan miss it?
Answers
- Five:
www,mail,vpn,jenkinsandshop. www(2001:db8::10).shopis a CNAME (an alias) forwww. The resolver follows the alias and returnswww’s addresses. The CNAME run showsCNAME=www.lab.example.blogis a CNAME toghost-lab.example.net, a host that does not exist. The default lookups (A and AAAA) find no address, so the name looks empty. With CNAME lookups, subenum shows the alias and flags itTAKEOVER?=dangling: the alias points at a name nobody has registered. If that were a cloud service, whoever claimed the name would controlblog.lab.example. This is how subdomain takeovers start. See takeover hints in the README.
Lab 2: spot the wildcard
subenum $W -simulate-zone $L/lab2-wildcard.zone lab.example
- What does subenum report, and why does it refuse to continue?
- Rerun with
-force. Which names are reported now, and how does subenum tell them apart from the wildcard?
Answers
- “Wildcard DNS detected”: the zone has a
*record, so every name, even a random one, resolves to192.0.2.250. Without filtering, every word in the list would be reported as a “finding”. www,portalandintranet. Before scanning, subenum queries several random names to fingerprint the wildcard’s answer. With-forceit then drops every result that matches that fingerprint; these three have their own addresses, so they are real hosts. Thewildcard-filteredline counts the rest.
Lab 3: rate limits
This resolver answers REFUSED once it gets more than 40 queries in a second, as many public and corporate resolvers do.
subenum $W -simulate-zone $L/lab3-rate-limit.zone -stats lab3.json lab.example
subenum $W -simulate-zone $L/lab3-rate-limit.zone -stats lab3-paced.json -rate 30 lab.example
- Compare the
refusedcounts of the two runs. - Open both
-statsreports. What is each run’sverdict? - Why can the first run still list some real names, and why should you not trust it anyway?
Answers
- The first run bursts with 100 workers and most of its lookups are refused.
The paced run (
-rate 30, under the resolver’s 40-per-second limit) has none. unreliablefor the burst,completefor the paced run.- The first queries arrived before the limit kicked in, so whatever they found is real. But a REFUSED name was never actually checked: it might exist. A run with many refusals says nothing about the names it missed, which is why the report calls it unreliable. Slower is faster here.
Lab 4: recursive discovery
subenum $W -simulate-zone $L/lab4-recursive.zone lab.example
subenum $W -simulate-zone $L/lab4-recursive.zone -recursive -depth 3 lab.example
- How many names does the flat scan find? How many does the recursive scan find?
- Which name needed
-depth 3? - Try the recursive scan with the bundled 5,000-word list (drop
$W). What happens, and why?
Answers
- Three (
www,dev,corp) against eight. Recursion takes every name it finds and enumerates the wordlist under it, which turns upapi.dev,git.dev,staging.devandvpn.corp, thenadmin.staging.dev. admin.staging.dev, three labels belowlab.example.- subenum refuses to start: with 5,000 words, depth 3 could mean over 100
billion queries. Recursion multiplies the wordlist at every level, so it
asks you to set
-max-queries(or-force) first. Against a real target, that guard is what keeps a scan from becoming a flood.
Lab 5: when lookups fail
subenum $W -simulate-zone $L/lab5-failures.zone -timeout 300 -stats lab5.json lab.example
- Which outcome lines in the breakdown are non-zero besides
resolvedandnxdomain? - What is the run’s
verdictinlab5.json, and what does it mean for the names that failed?
Answers
timeout(legacyandoldnever answer),refused(beta) andother(backupanswers SERVFAIL).degraded: the scan finished, but some names could not be checked. A failed lookup is not “does not exist”. Those names need a retry (-attempts) or a different resolver before you can rule them out.
Writing your own scenario
A scenario is a plain text file. Names are relative to the domain you scan,
and # starts a comment:
$DELAY 20ms # every answer waits this long
$REFUSE-ABOVE 50 # answer REFUSED past 50 queries a second
@ A 192.0.2.1 # the scanned domain itself
www A 192.0.2.10
www AAAA 2001:db8::10
shop CNAME www # relative target
blog CNAME ghost.example.net. # absolute target (trailing dot)
*.dev A 192.0.2.99 # wildcard under dev
slow TIMEOUT # never answered
broken SERVFAIL # also: REFUSED, NXDOMAIN
Names the file does not define are NXDOMAIN. The scenario is the whole world
during a lab, so names outside the scanned domain (such as an external CNAME
target) are NXDOMAIN too. Use the
documentation ranges (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24,
2001:db8::/32) and reserved names such as lab.example, so nothing in a
lab points at a real host.
Scenarios work with every other flag, such as -dL (each listed domain gets
the same zone), -permute, -diff, -format json and -stats, which
records the scenario file under zone. -simulate-zone cannot be combined
with -simulate, -dns-server or -r, whether they come from the command
line, a SUBENUM_* variable or the config file: a lab run refuses to start
rather than send a query off the machine.