Skip to content

DNS

Domains & Subdomains covers DNS as a recon source (using public resolvers to enumerate the target’s footprint). This page covers DNS as an active service - when the target itself runs a public DNS server and you can interrogate it directly. The key distinction: now you’re testing the server, not just using DNS to learn about other servers.

# 1. Banner / version probe
dig CH TXT version.bind @<target>
# 2. NS record enumeration
dig NS <domain> @<target>
# 3. ANY query (any records the server will disclose)
dig ANY <domain> @<target>
# 4. AXFR zone transfer - the big prize
dig AXFR <domain> @<target>
# 5. Subdomain brute-force against this specific NS
dnsenum --dnsserver <target> -f wordlist.txt <domain>

Success indicator: zone transfer succeeds (returns the full zone file), version banner reveals a vulnerable BIND version, or subdomain brute-forcing yields internal hostnames not visible via public resolvers.

DNS runs primarily on UDP 53 (queries < 512 bytes) and falls back to TCP 53 for larger responses or zone transfers. Both must be reachable for full functionality.

RoleWhat it does
AuthoritativeHolds the actual records for a zone. Answers definitively for the zones it serves.
Recursive resolverLooks up names on behalf of clients. Queries authoritative servers, caches responses.
ForwarderLike a recursive resolver but forwards queries to another resolver rather than walking the DNS tree itself.
CachingSubset of recursive - caches what it learns to avoid repeat queries.
StubLightweight client that just forwards to one or more configured resolvers.

Most operator-facing DNS work targets authoritative servers (which know things about a specific zone). Recursive servers exposed to the public internet are an attack surface in themselves (cache poisoning, amplification DoS) but those classes are out of scope for footprinting.

TypeWhat it returns
AIPv4 for the queried name
AAAAIPv6 for the queried name
MXMail servers for the domain, with priority
NSAuthoritative name servers for the domain
CNAMEAlias (queried name → another name)
TXTArbitrary text. Heavily used for SPF, DMARC, DKIM, SaaS verification tokens.
PTRReverse lookup (IP → name). Lives in in-addr.arpa and ip6.arpa zones.
SOAStart of Authority - admin email, refresh/retry/expire/TTL for the zone
SRVService location (used by Active Directory: _ldap._tcp.dc._msdcs.<domain>)
CAAWhich CAs may issue certs for the domain
  • DNS over TLS (DoT) - DNS wrapped in TLS, TCP 853
  • DNS over HTTPS (DoH) - DNS wrapped in HTTPS, typically over an existing 443 connection
  • DNSCrypt - pre-DoH alternative, less common

Encrypted DNS is a client/resolver concern; authoritative servers still speak plain DNS on 53.

BIND9 is the most common DNS daemon on Linux. Its main configuration files:

/etc/bind/named.conf # main config, includes other files
/etc/bind/named.conf.local # local zones (your zones)
/etc/bind/named.conf.options # global options (allow-query, recursion, etc.)
/etc/bind/named.conf.log # logging
/etc/bind/db.<zone> # zone files (one per zone)
/etc/bind/db.<reverse-zone> # reverse-lookup zone files

A typical named.conf.local entry:

zone "domain.com" {
type master;
file "/etc/bind/db.domain.com";
allow-update { key rndc-key; };
};

A zone file (/etc/bind/db.domain.com):

$ORIGIN domain.com
$TTL 86400
@ IN SOA dns1.domain.com. hostmaster.domain.com. (
2001062501 ; serial
21600 ; refresh after 6 hours
3600 ; retry after 1 hour
604800 ; expire after 1 week
86400 ) ; minimum TTL of 1 day
IN NS ns1.domain.com.
IN NS ns2.domain.com.
IN MX 10 mx.domain.com.
IN MX 20 mx2.domain.com.
IN A 10.129.14.5
server1 IN A 10.129.14.5
server2 IN A 10.129.14.7
ns1 IN A 10.129.14.2
ns2 IN A 10.129.14.3
ftp IN CNAME server1
www IN CNAME server2

The reverse-lookup zone file maps IPs back to names. For the 10.129.14.0/24 subnet:

$ORIGIN 14.129.10.in-addr.arpa
$TTL 86400
@ IN SOA dns1.domain.com. hostmaster.domain.com. (...)
IN NS ns1.domain.com.
IN NS ns2.domain.com.
5 IN PTR server1.domain.com.
7 IN PTR server2.domain.com.

You won’t read these files directly during external footprinting - but if you AXFR a zone, what you get is functionally the contents of the zone file.

Found in named.conf options (global or per-zone):

SettingWhat it allows
allow-query { any; }Anyone can query the server. Sometimes intentional, sometimes too permissive.
allow-recursion { any; }Server will perform recursive lookups for anyone. Enables DNS amplification attacks and cache poisoning.
allow-transfer { any; }Anyone can AXFR - full zone download. Disastrous when zones contain internal hostnames.
zone-statistics yesExposes per-zone query statistics - sometimes via version.bind style queries.
Missing version "[hidden]";Version is disclosed via dig CH TXT version.bind. Helps CVE matching.

The big one is allow-transfer { any; } (or specifying a too-wide subnet). Zone transfers are intended for slave nameservers to sync from masters; if accessible to the public, you get a complete listing of every record in the zone.

DNS servers often return their software version in response to CHAOS class TXT queries:

Terminal window
dig CH TXT version.bind @10.129.120.85
;; ANSWER SECTION:
version.bind. 0 CH TXT "9.10.6-P1"
;; ADDITIONAL SECTION:
version.bind. 0 CH TXT "9.10.6-P1-Debian"

That’s BIND 9.10.6-P1 on Debian. Cross-reference with CVE list for BIND9.

Some servers also respond to:

Terminal window
dig CH TXT hostname.bind @<target>
dig CH TXT authors.bind @<target>
dig CH TXT id.server @<target>

Or for PowerDNS:

Terminal window
dig CH TXT version.pdns @<target>

If version "hidden"; is set in BIND, you get either REFUSED or a fake version string. Either way: the server’s defenders are paying attention.

Terminal window
dig NS inlanefreight.htb @10.129.14.128
;; ANSWER SECTION:
inlanefreight.htb. 604800 IN NS ns.inlanefreight.htb.
;; ADDITIONAL SECTION:
ns.inlanefreight.htb. 604800 IN A 10.129.34.136

You’ve found another DNS server (ns.inlanefreight.htb at 10.129.34.136). Each NS in the result is a server worth investigating - different NSes often have different configs, and the one with allow-transfer { any; } is hiding somewhere on this list.

Terminal window
dig ANY inlanefreight.htb @10.129.14.128
;; ANSWER SECTION:
inlanefreight.htb. 604800 IN TXT "v=spf1 include:mailgun.org ..."
inlanefreight.htb. 604800 IN TXT "atlassian-domain-verification=..."
inlanefreight.htb. 604800 IN TXT "MS=ms97310371"
inlanefreight.htb. 604800 IN SOA inlanefreight.htb. root.inlanefreight.htb. ...
inlanefreight.htb. 604800 IN NS ns.inlanefreight.htb.
;; ADDITIONAL SECTION:
ns.inlanefreight.htb. 604800 IN A 10.129.34.136

Note: modern BIND versions sometimes refuse ANY queries with RFC8482-style minimal responses. If ANY returns very little, the server is RFC8482-compliant - query each type explicitly instead:

Terminal window
for type in A AAAA MX NS TXT SOA SRV; do
echo "=== $type ==="
dig $type inlanefreight.htb @<target> +short
done

The big one. Try every NS you’ve found:

Terminal window
dig AXFR inlanefreight.htb @10.129.14.128
inlanefreight.htb. 604800 IN SOA inlanefreight.htb. root.inlanefreight.htb. ...
inlanefreight.htb. 604800 IN TXT "MS=ms97310371"
inlanefreight.htb. 604800 IN NS ns.inlanefreight.htb.
app.inlanefreight.htb. 604800 IN A 10.129.18.15
internal.inlanefreight.htb. 604800 IN A 10.129.1.6
mail1.inlanefreight.htb. 604800 IN A 10.129.18.201
ns.inlanefreight.htb. 604800 IN A 10.129.34.136
inlanefreight.htb. 604800 IN SOA inlanefreight.htb. root.inlanefreight.htb. ...
;; XFR size: 9 records

That’s the complete zone. Every record. Internal hostnames (internal.inlanefreight.htb) and internal IPs (10.129.1.6) leaked in one query.

If AXFR works on one zone, try discovered subdomains too - they’re often separate zones with their own (possibly even worse) configurations:

Terminal window
dig AXFR internal.inlanefreight.htb @10.129.14.128
internal.inlanefreight.htb. 604800 IN SOA inlanefreight.htb. ...
dc1.internal.inlanefreight.htb. 604800 IN A 10.129.34.16
dc2.internal.inlanefreight.htb. 604800 IN A 10.129.34.11
mail1.internal.inlanefreight.htb. 604800 IN A 10.129.18.200
ns.internal.inlanefreight.htb. 604800 IN A 10.129.34.136
vpn.internal.inlanefreight.htb. 604800 IN A 10.129.1.6
ws1.internal.inlanefreight.htb. 604800 IN A 10.129.1.34
ws2.internal.inlanefreight.htb. 604800 IN A 10.129.1.35
wsus.internal.inlanefreight.htb. 604800 IN A 10.129.18.2
internal.inlanefreight.htb. 604800 IN SOA inlanefreight.htb. ...
;; XFR size: 15 records

dc1.internal.* and dc2.internal.* - two domain controllers visible. vpn.internal.*, wsus.internal.* (Windows Server Update Services), workstations ws1/ws2. A full internal map of a Windows shop, leaked via DNS.

If AXFR fails you’ll see ; Transfer failed. - try other NS servers, and try the subdomain trick (one zone may allow it even when the parent doesn’t).

Less common, but if AXFR is refused IXFR sometimes works:

Terminal window
dig IXFR=0 inlanefreight.htb @<target>

IXFR=0 asks for everything since serial 0 - which is everything. Some misconfigured servers allow IXFR but not AXFR.

When zone transfer doesn’t work, you can still discover subdomains by querying for each candidate against the authoritative server:

Terminal window
for sub in $(cat /opt/useful/seclists/Discovery/DNS/subdomains-top1million-110000.txt); do
dig $sub.inlanefreight.htb @10.129.14.128 +short \
| grep -E '^[0-9]' \
&& echo "$sub.inlanefreight.htb"
done | tee subdomains.txt

Or use dnsenum:

Terminal window
dnsenum --dnsserver 10.129.14.128 --enum -p 0 -s 0 \
-o subdomains.txt \
-f /opt/useful/seclists/Discovery/DNS/subdomains-top1million-110000.txt \
inlanefreight.htb

The advantage of querying the authoritative server directly: it knows about subdomains that public resolvers may not cache, and it doesn’t apply the same rate-limiting that big resolvers do.

Once you have a range of IPs the target uses, reverse-lookup the whole range:

Terminal window
# For 10.129.14.0/24
for i in $(seq 1 254); do
host 10.129.14.$i | grep -v "not found"
done
1.14.129.10.in-addr.arpa domain name pointer gateway.inlanefreight.htb.
5.14.129.10.in-addr.arpa domain name pointer mail.inlanefreight.htb.
136.14.129.10.in-addr.arpa domain name pointer ns.inlanefreight.htb.

Hosts that have PTR records (because their admins set them up cleanly) reveal themselves. Hosts without PTRs are still there but harder to attribute.

If the zone is DNSSEC-signed, the NSEC records define ranges of “names that don’t exist.” Each record points to the next existing name in lexicographic order. By walking the chain, you can enumerate every name in the zone without zone transfer:

Terminal window
# Query for a definitely-nonexistent name to get the NSEC response
dig +dnssec aaaaa.inlanefreight.htb @<target>

Look for the NSEC record in the response - it points to the next existing name. Query for that next name, follow the chain. NSEC3 adds hashing to make this slower but tools like nsec3walker can still enumerate it offline given enough samples.

Version → CVE → exploit:

  1. dig CH TXT version.bind → vulnerable BIND version
  2. Check CVE database, find exploit
  3. Test (carefully - DNS exploitation can DoS the server)

Zone transfer → internal hostnames → service enumeration:

  1. AXFR succeeds → complete record list
  2. Note internal hostnames (dc1.internal.*, mail1.internal.*, etc.)
  3. Each one is a new target for the rest of the services cluster

Brute-force → forgotten dev/staging/internal subdomains:

  1. Public crt.sh + DNS recon gave you N subdomains
  2. dnsenum brute against authoritative NS gives you N+M
  3. The M-many “extras” are typically the most under-protected (forgotten environments)

Reverse PTR sweep → asset inventory:

  1. Identify IP ranges the org uses
  2. Reverse-lookup the whole range
  3. Catalog the named hosts - usually a richer view than forward DNS gives
TaskCommand
Service scannmap -sV -sC -p53 <target>
Version probedig CH TXT version.bind @<target>
All NSdig NS <domain> @<target>
ANY querydig ANY <domain> @<target>
AXFRdig AXFR <domain> @<target>
AXFR all NSesfor ns in $(dig +short NS <domain>); do dig AXFR <domain> @$ns; done
Subdomain brutednsenum --dnsserver <target> -f wordlist.txt <domain>
Reverse sweepfor i in $(seq 1 254); do host 10.0.0.$i; done
Specific recorddig <type> <name> @<target> +short
TCP querydig <type> <name> @<target> +tcp
Trace from rootdig +trace <name>