dnsprobe v1

DNS poisoning, cache poisoning, and DNSSEC

· ~4 min read · dnsprobe.net/blog

DNS was specified in 1987 with no integrity protection. A recursive resolver's only assurance that the answer it received came from the authoritative server was the matching transaction ID (16 bits) and source port (often fixed). Forge a UDP packet with the right transaction ID before the legitimate answer arrives, and the resolver caches your forged answer. That is the entire mechanic of cache poisoning, and the security story since then has been a sequence of mitigations stacked on top of a fundamentally trusting protocol.

The Kaminsky attack

In 2008, Dan Kaminsky published the modern formulation of cache poisoning. The trick was not poisoning a single name — it was poisoning the delegation. Ask the resolver for random-1.example.com, random-2.example.com, etc. For each query, race the authoritative server with a forged response that not only answers the random name but also includes "and the authoritative NS for example.com is evil.attacker.net" in the additional section.

With a fast enough flood and a slow enough authoritative server, the attacker eventually wins the race for one of the random queries. The poisoned NS record now sits in the resolver's cache, and every subsequent lookup for example.com goes through the attacker's server.

The mitigations every resolver runs today

  1. Source port randomisation. Each outgoing query uses a fresh, random ephemeral source port. The attacker now has to guess both the 16-bit transaction ID and the 16-bit source port — 32 bits of entropy instead of 16. A practical attack now requires billions of packets per second, not millions.
  2. 0x20 case randomisation. The resolver randomises the case of the query name (ExAmPlE.cOm). The authoritative server echoes the case in its response. An attacker forging an answer has to guess the case sequence too, adding ~10-15 bits of entropy depending on name length.
  3. Strict bailiwick checking. Modern resolvers ignore additional-section glue that is outside the bailiwick of the responding server. A response from .com servers cannot inject NS records for example.org.

These mitigations push the cost of a successful poisoning attack from "feasible from a single ISP" to "requires nation-state-level infrastructure". They do not eliminate the attack.

DNSSEC, the cryptographic solution

DNSSEC (DNS Security Extensions, RFCs 4033-4035) introduces a chain of cryptographic signatures from the root down to each zone:

  1. Each zone signs its records with a Zone Signing Key (ZSK). The signatures are stored as RRSIG records alongside the data.
  2. Each zone's ZSK is itself signed by a Key Signing Key (KSK). The KSK's public part is in a DNSKEY record at the zone apex.
  3. The parent zone publishes a DS (Delegation Signer) record containing a hash of the child zone's KSK. The parent's DS is signed by the parent's ZSK.
  4. The root zone's KSK is published out-of-band and is hard-coded into every validating resolver as a trust anchor.

To validate a response, the resolver walks the chain: verify the answer's RRSIG with the zone's ZSK, verify the ZSK with the KSK, verify the KSK against the parent's DS, verify the parent's RRSIG up the chain, terminate at the root's pre-installed key.

What DNSSEC actually proves

It proves authenticity and integrity of the answer: this data came from the zone's authoritative key, and was not modified in transit. It also proves non-existence (via NSEC/NSEC3 records signed alongside the zone) — the resolver can verify that "no such name exists" is genuinely from the zone, not a forgery.

It does not provide confidentiality. DNSSEC responses are still in cleartext over the wire. Anyone watching the network sees every name you look up. Confidentiality is a separate problem solved by DoH/DoT.

It does not protect the last mile between the recursive resolver and the client stub by default. If your laptop's stub resolver does not validate (most don't), a DNSSEC-validating recursive resolver gives you a signed answer and then a non-validating stub accepts whatever IP arrives on the wire.

Why adoption is uneven

As of 2025, DNSSEC validation is widespread at the recursive layer (Cloudflare, Google, Quad9, Comcast — all validate). DNSSEC signing at the authoritative layer is much spottier:

  • Most TLDs are signed. The .com zone is signed. The root is signed.
  • About 4% of .com-zone second-level domains are signed.
  • Major CDNs (Cloudflare, Akamai) support DNSSEC for hosted domains and have made it one click.
  • Smaller DNS providers and self-hosted BIND/NSD setups often do not sign by default.

The reason is operational: a botched DNSSEC rollout takes the entire zone offline at validating resolvers. Wrong key rotation, expired RRSIG, missing DS at the parent — any of these returns SERVFAIL for the whole zone. Small teams that have ever felt the pain choose not to sign.

Checking it

From the command line:

# Does your recursive resolver validate?
dig +short example.com txt
dig +short example.com txt +cd       # cd = checking disabled

# If the first returns a different answer than the second for a bogus domain,
# your resolver is validating.

dig dnssec-failed.org +short          # validating resolver returns SERVFAIL
dig dnssec-failed.org +short +cd      # returns an answer (validation disabled)

To check whether a zone is signed:

dig DNSKEY example.com +short
dig DS example.com +short            # check the parent for a DS record

To see DNSSEC status alongside the regular records, run a host through dnscheck — the DNS panel surfaces SOA + NS + the chain status. Try cloudflare.com (signed) versus example.com (not signed at the second level).

References: RFC 4033, Cloudflare's DNSSEC writeup.