Zone Dumping via DNSSEC
Walk your dog, and a domain simultaneously
Would you store sensitive data in an unauthenticated, unencrypted, globally-distributed database? I wouldn't, but best-practice or not, sysadmins store... interesting things in DNS records. Even with clean DNS records, it's still best practice to harden your DNS servers to prevent zone transferring from unauthorised sources lest someone obtain a dump of every record for your domain:
$ dig axfr zonetransfer.me @nsztm1.digi.ninja zonetransfer.me. 7200 IN SOA nsztm1.digi.ninja. robin.digi.ninja. 2019100801 172800 900 1209600 3600 zonetransfer.me. 300 IN HINFO "Casio fx-700G" "Windows XP" zonetransfer.me. 301 IN TXT "google-site-verification= tyP28J7JAUHA9fw2sHXMgcCC0I6XBmmoVi04VlMewxA" ...
Most reputable companies block zone transfers, so adversaries need to solicit records with methods such as brute-forcing, using wordlists, certificate transparency logs, APIs... Whilst this may recover many records:
- It's very noisy
- Has diminishing returns
- There is no guarantee the results are complete
- Good luck finding complex records e.g
you-will.never.find-me-3849248392423.example.comif there's no associated TLS certificate.
But what if I told you there is a guaranteed way to retrieve the zone for all DNSSEC domains - even if they're impervious to
AXFR zone transfers? Would you like a zone dump of
- Brief theory on NSEC / NSEC3
- Practically walking a DNSSEC zone
- Automated dumping tool release
- Whitepaper drop
NSEC (Next Secure) Records
Firstly, DNSSEC (DNS Security) is a misnomer, a better name is DNSAUTH (DNS Authentication) because the long and short of it is that DNSSEC signs records verifiable through a chain of trust. DNSSEC is not encryption. One issue the implementors grappled with is how do you a sign the response to a query for a record that doesn't exist?
Ignoring DNSSEC, if you were to lookup a non-existent record, you'd get a DNS
NXDOMAIN (non-existent domain) error.
$ dig a non-existent.paypal.com @ns1.p57.dynect.net. status: NXDOMAIN
With a DNSSEC-aware resolver, if you were to lookup an existing record you will get the answer, and the
RRSIG (record set signature) to authenticate the answer and make sure it wasn't tampered.
$ dig a www.paypal.com +dnssec @ns1.p57.dynect.net. CNAME www.glb.paypal.com. RRSIG CNAME Gt...SIGNATURE...3Y=
Now combine the two, what happens if you lookup a non-existent record with a DNSSEC-aware resolver?
$ dig non-existent.paypal.com +dnssec @ns1.p57.dynect.net. ndm-ssp.paypal.com. NSEC notifications.paypal.com. CNAME RRSIG NSEC
So what just happened? Well you can't sign an empty null record, but you also can't not authenticate the empty response because whilst a MitM might not be able to tamper DNS answers, they could
NXDOMAIN all requests known as a "denial of existence" attack.
DNSSEC's solution to authenticate non-existent records is to return a range for which there are no records. See our example above, we looked up
non-existent and paypal responded with "I don't have any records between
notifications". The RFC requires these ranges are returned in alphabetical order, and that they wrap. As a bonus, it also gives all the record types for the start of the range, now we know that
ndm-ssp only has a
CNAME record (ignoring the
If you've been paying attention, you may ask "can't you linearly walk subdomains by iteratively looking up non-existent domains, collecting the ranges, then looking up the records to retrieve every record for a domain" to which I say, yes you can astute reader.
... $ dig a 0.devblog.paypal.com +dnssec @ns1.p57.dynect.net. devblog.paypal.com. NSEC developer.paypal.com. A RRSIG NSEC $ dig a 0.developer.paypal.com +dnssec @ns1.p57.dynect.net. developer.paypal.com. NSEC disputes.paypal.com. CNAME RRSIG NSEC $ dig a 0.disputes.paypal.com +dnssec @ns1.p57.dynect.net. disputes.paypal.com. NSEC dl.paypal.com. CNAME RRSIG NSEC ...
NSEC3 attempts to curb this crawling by hashing returned ranges:
$ dig a non-existent.discover.com +dnssec @a3-64.akam.net. 7qmeaqom... NSEC3 7T763RTH... CNAME RRSIG
Thus, we can no longer linearly crawl. But as the salt, iterations, algorithm and plaintext are known, we can hash candidates locally. Once a hash is with a range we're yet to observe, we fire that off to the nameserver and rinse and repeat until we've got all hashes for offline cracking:
$ dig a 0000.discover.com +dnssec @a3-64.akam.net. sb0118ts... NSEC3 SBBLTA70... CNAME $ dig a 5431.discover.com +dnssec @a3-64.akam.net. bh8p1p2b... NSEC3 BINKA7GO... CNAME ... $ dig a fd8e.discover.com +dnssec @a3-64.akam.net. u3ab2cdu... NSEC3 U4MV09Q2... A MX TXT
There are a few items of note:
- Hashes are salted with a salt and the domain, so rainbow tables are of no use
- It uses SHA-1 so cracking is speedy
- You can estimate the zone size by calculating the average % coverage of returned ranges against the
- E.g using this info you can calculate Salesforce has ~25,000,000 deployments
Automation / Tool Drop
- NSEC walking + dumping
- NSEC3 walking
- NSEC3 post-crack dumping
It's been a bucket list item of mine to write a "proper" whitepaper and publish it by myself. So I'm proud to present: Taking the DNS for a Walk; NSEC3 Prevalence and Recoverability. It goes info far more detail than this blog post and includes some internet-wide research. If you're not convinced, here's the abstract.
Machines querying for non-existent names within the Domain Name System (DNS) are met with a Non-Existent Domain (NXDOMAIN) error. Within the context of DNS Security Extensions (DNSSEC), an authenticated negative answer is returned containing both the lexicographically prior and following existent names within the Next Secure (NSEC) DNS record. NSEC provides these values in plaintext allowing for linear DNS zone walking. NSEC3 instead hashes these neighbouring existent names in an attempt to limit DNS record disclosure. This paper presents a GPU-based attack on NSEC3 that recovered 44% of names for the internet’s top 20,000 NSEC3-protected DNS zones, partially invalidating NSEC3’s privacy and security goals.