AXFR (DNS zone transfer) discovery
A plain subnet scan sends no SNI, so behind an SNI-routed frontend — an
ingress controller, a NetScaler content switch, an HAProxy/nginx virtual-host, an
ALB — it sees at most one of the certificates served on that IP:port (usually
a default/placeholder cert, or a handshake failure when the server enforces
SNI). The other certificates, and their expiry/rotation, stay invisible.
AXFR discovery solves this the authoritative way: it transfers your internal
DNS zones, learns every hostname → IP mapping, and then connects to each IP with
the hostname as the SNI — so the frontend routes to the right virtual host and
returns that host's real certificate. No guessing, no wordlists: the zone is the
inventory.
When to use it
Use AXFR discovery when you control the internal DNS and services sit behind SNI-based routing. It is the only way to reliably enumerate distinct-per-hostname certificates on a shared IP:port. If your hosts have their own IPs (one cert per IP), a plain CIDR scan is enough.
How it works
Settings → AXFR Resolvers (admin: DNS servers + zones they are authoritative for)
│ an AXFR source references these
▼
zone transfer (AXFR) → A records + CNAME flattening → (hostname → IPv4)
▼ filter: IPv4 only; drop metadata/link-local/loopback/CGNAT; drop public unless allowed
▼ group by IP:port (many hostnames on one IP = an SNI-routed frontend)
▼ TLS handshake to each IP with hostname as SNI → the host's real certificate
▼ → normal inventory, findings, change detection, notifications
Because the transfer pairs each name with its IP, the scanner always presents the correct SNI and retrieves each distinct certificate in one pass. SAN-snowball and reverse-DNS still run on top (when deep SNI is on), so a cert whose SANs reference names not in the zone is also caught.
Step 1 — enable zone transfer on your DNS server
AXFR only works against the server that is authoritative for the zone (holds the primary/secondary copy). You cannot transfer from a forwarder or a stub zone — those hold no host records. Point CertAutoPilot at the authoritative server, and allow transfers from the scanner's IP there. No TSIG is used — access is controlled by the DNS server's IP allow-list.
Windows Server DNS
- Find the authoritative server: on a stub zone its Name Servers tab lists
them, or run
nslookup -type=soa <zone>. - On that server open DNS Manager → Forward Lookup Zones → the zone → Properties → Zone Transfers.
- Tick Allow zone transfers → Only to the following servers → add the CertAutoPilot scanner IP(s).
- Repeat for any reverse zone (
x.x.x.in-addr.arpa) if you want PTR bootstrap.
AD-integrated zones have transfers off by default; this enables them per-zone and replicates to the other DCs.
CoreDNS
Use the transfer plugin with a to allow-list on the zone served by file/auto:
example.com:53 {
file /etc/coredns/zones/db.example.com
transfer {
to 10.0.0.9 10.0.0.10 # scanner IP(s) only
}
log
errors
}
to allow-lists the source IP of the AXFR request. to * allows everyone
(not recommended). On CoreDNS < 1.7.0 use the inline form: file db.example.com { transfer to 10.0.0.9 }.
BIND
zone "example.com" {
type master;
file "/etc/bind/db.example.com";
allow-transfer { 10.0.0.9; 10.0.0.10; }; # scanner IP(s) only
};
Verify before configuring CertAutoPilot
From the scanner host (so the request originates from the allow-listed IP):
dig axfr example.com @10.0.0.53
A full A-record list means the allow-list is correct. REFUSED / empty means the
source IP isn't allow-listed (mind NAT — the server sees the translated IP).
Step 2 — add the resolver in Settings
Settings → General → AXFR Resolvers (org-admin only — this is the trust boundary; operators can only select from vetted servers, never point the scanner at an arbitrary host). Add a row:
- Name — a label, e.g.
Internal DNS. - Servers — one or more
host:port(:53default) of the authoritative server(s). Multiple servers are an ordered failover list: if the first doesn't answer a zone, the next is tried. - Zones — the zones those servers are authoritative for, e.g.
example.com,internal.corp.
Step 3 — create an AXFR source
Discovery → Sources → New → type DNS Zone Transfer (AXFR):
- AXFR Resolvers — select one or more of the resolvers configured above.
- Ports — TLS ports to probe on each resolved host (default
443). - Allow Public IP Scan — off by default; when off, hostnames that resolve to a public IP are dropped (internal-only scanning).
- Deep SNI Discovery — leave on to also run SAN-snowball + reverse-DNS on top of the zone names (finds extra certs); the SNI candidate budget caps probes per IP.
- Schedule / Max targets — as for any source. Target count is unknown until the transfer runs, so there is no live estimate.
Security & safety
- SSRF-guarded. Every scan target IP is pinned into the outbound network policy; cloud-metadata, link-local, loopback, unspecified and CGNAT addresses are always dropped even if the zone returns them. The resolver server is resolved and validated once and dialed by pinned IP (no DNS rebinding).
- Admin-vetted servers. Only org-admins define AXFR resolver servers; creating a source can only reference that vetted list.
- Bounded. Transfers stream with a total-time deadline (anti-slowloris) and a record cap, so a hostile/huge zone can't exhaust the worker.
- No TSIG. Access is the DNS server's IP allow-list — add/remove the scanner IP there to grant/revoke.
Partial coverage
If some zones transfer and others fail (refused, timeout), the run is reported as partial and the disappearance sweep is skipped that run — so endpoints whose zone couldn't be transferred are never falsely marked "gone". A fully-clean run resumes normal disappearance detection. Per-zone failures are logged to the job log.
Limits
- IPv4 only — AAAA records are skipped (IPv6 CIDR/target support is out of scope).
- Requires AXFR permission — if the DNS server won't allow transfers from the scanner (no allow-list entry, cloud DNS that forbids AXFR), this source can't run; use a CIDR scan instead.
- Split-horizon — point the resolver at the internal view so names map to internal IPs; an external-view server returns public IPs that get filtered out (surfaced as "N records, 0 scannable").
- Names not in DNS — a host with no A record (dynamic, IP-only) can't be enumerated; that's inherent to a name-first approach.
Troubleshooting
Job log: "AXFR refused/failed for zone …"
The DNS server rejected the transfer. Add the scanner IP to the zone's transfer allow-list (Step 1), and confirm you targeted the authoritative server, not a forwarder/stub.
Job log: "transferred N records but 0 were scannable"
The zone resolved, but every IP was filtered — usually because the resolver is the external (split-horizon) view returning public IPs while Allow Public IP Scan is off. Point the resolver at the internal DNS.
Run status "partial"
At least one zone failed to transfer (see per-zone warnings in the job log). Disappearance detection is skipped for safety until a clean run.