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:

  1. It's very noisy
  2. Has diminishing returns
  3. There is no guarantee the results are complete
  4. Good luck finding complex records e.g you-will.never.find-me-3849248392423.example.com if there's no associated TLS certificate.

But what if I told you there is a guaranteed way to retrieve the zone for all[1] DNSSEC domains - even if they're impervious to AXFR zone transfers? Would you like a zone dump of paypal.com, accenture.com, stanford.edu, fbi.gov...?

Agenda:

  1. Brief theory on NSEC / NSEC3
  2. Practically walking a DNSSEC zone
  3. Automated dumping tool release
  4. 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 ndm-ssp and 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 RRSIG and NSEC records).

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

NSEC3[2] 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:

  1. Hashes are salted with a salt and the domain, so rainbow tables are of no use
  2. It uses SHA-1 so cracking is speedy
  3. You can estimate the zone size by calculating the average % coverage of returned ranges against the 0000... - ffff... range
    • E.g using this info you can calculate Salesforce has ~25,000,000 deployments

Automation / Tool Drop

Say hi to "NSEC(3) Walker 🚶‍♂️". It has 3 modes (the README has far more detail 😉):

  1. NSEC walking + dumping
  2. NSEC3 walking
  3. NSEC3 post-crack dumping

Whitepaper Drop

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.