Denys Inhul

ch6: security and trust

date: May 14 2026

1. DNS trusts whatever bytes come back

At every layer of DNS, the receiver trusts the sender. Your laptop trusts its OS resolver. The OS trusts the recursive resolver. The recursive resolver trusts whatever authoritative server it queried. The authoritative server trusts its own zone file.

None of this trust is cryptographic. The protocol shipped with no built-in authentication. Bytes arrive, the resolver parses them, the data goes into the cache. If the bytes are a lie, the lie gets cached, and every client behind that resolver gets the lie until the TTL expires.

This was acceptable in 1987. The internet was small, the threat model was research-lab informal. By the late 1990s the assumption had stopped holding, and the consequences started showing up as practical attacks. The rest of this chapter is the catalog of attacks and the two repair efforts that followed: DNSSEC for authentication, and encrypted transports for privacy.

2. Cache poisoning and Kaminsky 2008

Cache poisoning is the natural consequence of unauthenticated responses. An attacker tricks a resolver into accepting a false answer and caching it. After that, every client behind the resolver gets the false answer for the rest of the TTL.

The attack has two parts. First, get the resolver to make a query you can answer. This is usually trivial: send the victim an email with embedded images from your domain, or get them to load any page that triggers the lookup. Second, race a forged response back to the resolver before the legitimate one arrives. The resolver matches incoming responses by query ID (16 bits), source port (16 bits), and the question section. If your forgery matches all three and arrives first, the resolver accepts it.

Pre-2008, only the 16-bit ID had to be guessed; source ports were typically fixed. A few thousand spoofed packets and you’d hit it eventually. Slow but reliable.

Kaminsky’s amplification

Dan Kaminsky’s 2008 work industrialized the attack. The clever part wasn’t poisoning a single name. It was attacking parent referrals via random subdomains.

To poison example.com, querying for example.com directly gives you only one race per cache window. So instead, you query for random1.example.com, random2.example.com, random3.example.com, etc. None of these are cached, so each triggers a fresh query to the authoritative server. For each, you race a forged response containing a glue A record claiming the authoritative server for example.com is now your machine. One race per name gives you thousands of attempts per minute. The expected time to a successful poisoning drops from years to minutes. And once you’ve poisoned the NS record for example.com, you control the entire zone, not one name.

The disclosure in August 2008 triggered a coordinated patch across every major resolver vendor: source-port randomization. Now the attacker has to guess one of 2^32 (ID × port) values, not 2^16. The attack went from “minutes” to “impractical for most attackers.”

But this is a difficulty patch, not a fix. The fundamental problem is still there: responses aren’t authenticated. A motivated attacker with enough bandwidth can still pull it off. The proper fix is to sign the responses.

[demo placeholder] Live attacker-vs-resolver race: visualize one query in flight, attacker fires N spoofed responses with random ID guesses; click “patch” to enable source-port randomization and watch the same attacker fail.

3. DNSSEC: signing the answers

The clean fix is cryptographic signatures. If every response is signed, a resolver can verify the signature regardless of where the bytes came from. The attacker has to forge a signature, not just a packet.

DNSSEC, defined in RFC 4033, RFC 4034, and RFC 4035 (2005, with updates since), does exactly this. Three record types carry most of the load:

RecordPurpose
DNSKEYThe public key used to verify signatures in this zone. Published in the zone itself.
RRSIGA signature over an RRset (all records with the same name and type), made with the zone’s private key. Resolvers verify these.
DSA hash of the child zone’s DNSKEY, published in the parent zone. This is the link in the chain of trust.

KSK and ZSK

In practice, each zone uses two keys, not one. The Key Signing Key (KSK) signs only the DNSKEY record set, has a long lifetime, and is the key the parent vouches for via DS. The Zone Signing Key (ZSK) signs everything else and rotates more frequently. The split exists so that you can change your day-to-day signing key (ZSK) without coordinating a DS update with your parent registry. KSK rollovers are rare and expensive; ZSK rollovers are cheap and common.

NSEC and NSEC3 for denying nonexistence

Signing existing records is the easy part. How do you sign “this name doesn’t exist”? You can’t sign nothing. The answer is NSEC: instead of returning an empty response for an NXDOMAIN, the server returns a signed record asserting “between aaa.example.com and ccc.example.com there are no names.” Anyone asking about bbb.example.com can verify that the gap is genuine.

The downside is that NSEC lets you walk the zone: ask the right sequence of queries and you get the full list of names. NSEC3 (added later) hashes the labels before sorting, which makes enumeration much more expensive but not impossible. Most major zones use NSEC3; some use the newer NSEC3 white-lies trick to prevent enumeration entirely at the cost of more CPU per response.

Algorithm choices

Original DNSSEC used RSA with SHA-256. ECDSA P-256 is now widely deployed and produces smaller signatures (64 bytes vs 256 for 2048-bit RSA), which matters because every signature inflates the response. Ed25519 is the modern preference where it’s supported. The choice cascades: smaller signatures mean less amplification surface for reflection attacks, faster validation, lower response sizes for fragmentation-sensitive paths.

The chain of trust

The verification chain runs from the root down:

  1. The resolver is born knowing the root’s DNSKEY (more precisely, a hash of it called a trust anchor).
  2. The root’s DNSKEY signs the root zone’s records, including the DS record for .com.
  3. The DS record for .com is a hash of .com’s DNSKEY. So the resolver now trusts .com’s DNSKEY.
  4. .com’s DNSKEY signs .com’s records, including the DS record for example.com.
  5. And so on, down to the leaf.

Each parent zone vouches for its children’s keys. The resolver only has to trust the root key. Everything else is verified by following the chain.

[diagram placeholder] Chain of trust from root DNSKEY down through .com’s DS/DNSKEY to a leaf A record’s RRSIG, with arrows showing what each layer signs.

The root key signing ceremony

The root key is managed through a public ceremony held quarterly at two highly secure facilities (El Segundo, California, and Culpeper, Virginia). Multiple Trusted Community Representatives physically open safes, retrieve smartcards, and participate in the key-generation and signing process. Hardware security modules ensure the private key never leaves the secure facility. The whole thing is filmed and published. The theater is the point. If you ever wondered “who actually controls the keys at the top of the internet’s trust chain?”, the answer is: this specific group of people, on these specific dates, on camera.

[demo placeholder] Live DNSSEC validator: type a signed domain, fetch DNSKEY/RRSIG/DS for each level, walk root → TLD → leaf, green check or red X with reason at each step. “Try a broken one” preset.

4. Why DNSSEC adoption is still slow

DNSSEC was first proposed in 1997. The root was signed in 2010. The protocol is mature. The cryptography is well-understood. APNIC reported only ~36% validation and ~7% secure delegation in 2025. After 25+ years.

None of the reasons are technical. DNSSEC is one of those technologies where the benefit is real, but the operational pain is also real.

Easy to break your domain. Key management, signatures, DS records at the parent zone, key rollovers, expiration handling, coordination between DNS host and registrar. A normal DNS mistake breaks one record. A DNSSEC mistake makes the entire domain look bogus to validating resolvers, and you can’t fix it until the bad signatures expire from caches.

Weak incentive. HTTPS already gives users the visible padlock. DNSSEC protects the DNS lookup, but users don’t see it. There’s no UI element, no browser indicator, no upsell. The site owner takes on operational risk for an invisible benefit.

Both sides need deployment. Domain owners need to sign their zones, and recursive resolvers need to validate. If only one side does it, the benefit is incomplete. Hence the asymmetric ~36% / ~7% numbers.

Doesn’t encrypt. DNSSEC authenticates DNS answers; it doesn’t hide what domain you looked up. For privacy, people look at DoH/DoT/DoQ (§7). To many non-DNS people, “secure DNS” sounds like it means privacy, and DNSSEC isn’t that.

Amplification and complexity. DNSSEC responses are larger (signatures + keys), which worsens amplification-attack potential. Key management exposes zone contents through NSEC walking. Cloudflare’s engineering writeups list these as the main DNSSEC complications they’ve shipped workarounds for.

WebPKI routed around it. In theory, DNSSEC + DANE could have reduced reliance on certificate authorities. In practice, browsers and CAs standardized around the current WebPKI model instead. So DNSSEC never became “required” for normal HTTPS websites; it’s a separate authentication path that the rest of the web didn’t end up using.

5. The rest of the attack catalog

Cache poisoning isn’t the only attack on or via DNS. A few others worth naming.

Amplification DDoS

Use DNS servers as megaphones to attack a third party. The attacker sends small spoofed queries (~50 bytes, source IP forged to the victim’s address) to open resolvers or misconfigured authoritative servers. The server responds with a large answer (~4000 bytes via EDNS, more with DNSSEC). The response goes to the victim. Amplification factor 50× to 100×. The 2013 Spamhaus attack hit ~300 Gbps using this. Open resolvers are the main vector; the defense is to not run open resolvers, plus source-address validation (BCP 38) at the network edge to prevent spoofing.

NXDOMAIN floods

Send a large number of queries for random subdomains of a target zone. The authoritative server has to confirm each subdomain doesn’t exist, burning CPU and outbound bandwidth. With DNSSEC enabled, each NXDOMAIN response requires generating an NSEC or NSEC3 record, which is more expensive still. Used against gambling sites, financial sites, political targets. Defense is rate-limiting and DNS-level firewalls.

Subdomain takeover

You set up blog.example.com as a CNAME pointing at a SaaS hosting service. Later you deprovision the hosted blog but forget to delete the CNAME. An attacker registers the abandoned hostname on the same SaaS service and now controls blog.example.com. They can serve content, obtain TLS certificates for it, run phishing. Defense is hygiene: keep CNAMEs synchronized with the resources they point at, and audit regularly.

Zone walking

If a zone uses NSEC for DNSSEC denial-of-existence, an attacker can enumerate the entire zone’s contents by walking the NSEC chain. NSEC3 makes this much harder by hashing the labels, but rainbow-table attacks on the hashes are still viable for short, predictable names. NSEC3 with “white lies” prevents it entirely at the cost of more CPU.

DNS tunneling

Encode arbitrary data into DNS queries and responses (typically as base32-encoded TXT records under a controlled subdomain). Used as a covert channel: malware C2, data exfiltration from networks that block direct internet access but allow DNS. Detection is by traffic analysis; DNS tunneling generates unusual query patterns that don’t match real lookups.

6. DNS rebinding

DNS rebinding deserves its own section because it doesn’t break DNS itself. It abuses how browsers interact with DNS to bypass the same-origin policy and reach into the user’s local network.

An aside on the browser security model

Browsers enforce the same-origin policy (SOP). JavaScript loaded from evil.com can only make HTTP requests to evil.com, with explicit exceptions like CORS-permitted cross-origin requests. The policy keys on the hostname.

CORS (Cross-Origin Resource Sharing) is the standard mechanism for legitimate cross-origin requests: the server you’re calling sends explicit headers saying “I accept requests from this origin,” and the browser enforces that allowlist. CORS works when the attacker’s hostname is different from the target’s. If evil.com tries to call 192.168.1.1, that’s a cross-origin request and the browser will (depending on the request type) either block it or require a preflight check.

Backend frameworks add a complementary defense: host allowlists. A typical Django, Rails, or Express setup ships with a list of acceptable Host headers, and requests with unknown Host headers get rejected. This catches requests where the attacker’s HTTP target IP is the right one but the hostname is forged.

The same-origin policy authenticates the hostname, not the IP. The attacker exploits the gap.

The attack

  1. Attacker registers evil.com. Configures the authoritative server to return their public IP, with TTL set to 1 second.
  2. Victim visits evil.com. Browser resolves to attacker’s IP, loads the page, runs the attacker’s JavaScript.
  3. The JavaScript waits a couple of seconds, then issues a fetch request to evil.com, the same origin it was loaded from.
  4. The browser’s DNS cache for evil.com has expired (TTL 1 second). The browser re-resolves.
  5. This time, the attacker’s authoritative server returns a different IP, say 192.168.1.1, the victim’s home router.
  6. The browser sends the HTTP request to 192.168.1.1 with header Host: evil.com. The same-origin policy says: same hostname, fine.
  7. The router receives the request and responds. The JavaScript can now read the response, submit forms to the router’s admin interface, exfiltrate data over the same channel.

The attacker’s JavaScript, loaded from a public website, is now talking to the victim’s home router. Or printer. Or any other device on the local network that exposes an HTTP interface. All from a single visit to a public website. The attack is 15+ years old and remains practical in 2026. It’s a recurring source of CVEs against IoT devices and home routers.

[demo placeholder] “What’s on your LAN?” probe: fetch requests to common router admin interfaces (192.168.1.1, 192.168.0.1, 10.0.0.1) via the rebinding trick; reports what responded. No exfiltration.

Why hasn’t this been fixed?

Several obvious-looking fixes have been tried; each runs into a load-bearing piece of the real world.

Why not pin the first IP? If evil.com first resolved to 1.2.3.4, make the browser refuse to switch IPs mid-session. The problem: legitimate infrastructure constantly changes IPs. CDN failover, DNS load balancing, mobile network handoffs, multi-region services, short TTL records, corporate split-horizon DNS, IPv4/IPv6 fallback, DNS-based traffic steering. Pin the first IP and you break all of it.

Why not block public-website→private-IP requests? Browsers are trying. Chrome’s Private Network Access work added preflight checks for requests from public sites to more-private network addresses. Rolling it out widely is hard because tons of real software depends on browser-to-LAN access: router setup pages, printer admin pages, Chromecast and smart-TV discovery, local dev servers (Vite, webpack, Jupyter), Docker, corporate intranet apps, IoT setup flows. Block all of that suddenly and a lot of legitimate stuff breaks.

Why not have resolvers reject private-IP answers for public names? Some routers and resolvers do this; it’s commonly called “DNS rebinding protection.” It false-positives on corporate VPNs, split-horizon DNS, internal SaaS, tunnels, dev environments. Blocking it everywhere causes real problems.

Why not rely on CORS? Because DNS rebinding is specifically designed to not look cross-origin. The fetch target is still evil.com from the browser’s POV. CORS protects you against fetch("http://192.168.1.1/admin") (cross-origin). It doesn’t catch the rebinding version, which is same-origin from the browser’s POV.

The real answer is layered defense. The browser does what it can (Private Network Access). The resolver or router does what it can (rebinding protection where safe). And the target server takes responsibility: strict Host-header allowlists, authentication, TLS, no trust just because the request came from LAN or localhost. The most reliable fix lives at the target server. Dev tools and local servers increasingly add “allowed hosts” checks for exactly this reason.

7. Encrypted DNS: DoH, DoT, DoQ

DNSSEC authenticates the answer. It doesn’t hide the question. Anyone on the path between the stub resolver and the recursive resolver (your ISP, your coffee shop’s WiFi, a state-level network observer) can see every name you look up, because the wire format is unencrypted UDP.

Three encrypted transports address this in different ways.

DoT: DNS over TLS

RFC 7858 (2016). DNS messages run over a persistent TLS connection on TCP port 853. Clean separation from web traffic, easy to identify the channel (and easy to block at the network layer, which is its weakness in censorship contexts). Widely supported in Android, in most consumer routers, in most public resolvers.

DoH: DNS over HTTPS

RFC 8484 (2018). DNS messages run over HTTPS on port 443. Indistinguishable from regular web traffic at the network layer. Much harder to censor without breaking HTTPS more broadly. Browsers (Firefox, Chrome) ship it directly, sometimes overriding the system resolver settings, which has been controversial in places where ISPs prefer their resolver to remain in the path.

DoQ: DNS over QUIC

RFC 9250 (2022). DNS messages run over QUIC, which is layered on UDP. Why QUIC matters specifically (vs plain UDP): QUIC gives you encryption (built-in TLS 1.3), reliability, and congestion control without TCP’s per-connection state and head-of-line blocking. 0-RTT session resumption brings the per-query overhead close to plain UDP economy while keeping encryption. Stream multiplexing means a single connection can carry many independent queries in parallel. DoQ is the most recently deployed and the most architecturally interesting, and likely the long-term winner if QUIC continues to spread.

What encrypted transports don’t do

The encryption protects the user-to-resolver leg. It doesn’t change anything beyond that. The recursive resolver itself still sees every query. The authoritative servers downstream still see whatever the resolver forwards (modulo QNAME minimization, Chapter 2). EDNS Client Subnet (Chapter 5) still leaks /24 prefixes to CDN auth servers.

And there’s a different privacy concern that DoH and DoT created on the way to solving the on-path-observer one: concentration. Most DoH traffic ends up at Cloudflare, Google, Quad9, NextDNS, or a handful of others. The on-path observer is gone, but the resolver operator sees more of your queries than any single party did before. Whether that’s a net win depends on your threat model and how much you trust the operator. The encrypted-DNS transition didn’t eliminate the privacy problem; it relocated it.

End of series. Back to series index.


Further reading