Module 2 · Lesson 3

Cache Poisoning and Spoofing

12 min read

dnssecuritycache-poisoningkaminskydnssecspoofing

Cache Poisoning and Spoofing

In July 2008, Dan Kaminsky discovered a fundamental flaw in DNS that could let an attacker poison any resolver's cache for any domain in a matter of seconds. He kept it secret for six months while coordinating the largest synchronized software patch in internet history. Eighteen vendors shipped patches simultaneously on July 8, 2008. The secret lasted another 17 days before it leaked.

Understanding the Kaminsky attack is important not because you'll reproduce it, but because it illustrates exactly why source randomization, DNSSEC, and resolver diversity matter. It's an elegant attack.

How DNS Cache Poisoning Works

A resolver caches answers to avoid re-querying for the same records. When a forged answer makes it into that cache, every user hitting that resolver gets the wrong answer until the TTL expires.

The basic poisoning flow:

  1. Attacker sends a query to the resolver for evil.example.com (or tricks a user into triggering one)
  2. Resolver hasn't cached this, so it goes to the authoritative nameserver
  3. Attacker sends a flood of forged responses, each claiming to be the authoritative answer
  4. If a forged response arrives before the real one, and matches the transaction ID, the cache is poisoned

The obstacle in step 4: transaction IDs. Each DNS query has a 16-bit transaction ID. The resolver will only accept a response that matches the ID of the outstanding query. 16 bits = 65,536 possible values.

Before Kaminsky, attacking this required waiting for the cache to expire (sometimes hours or days), then racing your forged response against the real one. Slow. Unreliable.

The Kaminsky Attack: Why It Was Different

Kaminsky found a way to iterate rapidly without waiting. Here's the trick:

Standard poisoning targets the A record for a specific name. But forged responses can include additional records — the "additional section" and "authority section" of a DNS response. Specifically, you can include NS records for the zone itself.

If you poison the NS records for example.com (not just a single hostname), you've poisoned the entire zone. Every query for any subdomain of example.com returns from your attacker-controlled nameserver.

The iteration trick: instead of waiting for the example.com cache to expire and retrying, Kaminsky's attack queries for random subdomains: a1.example.com, a2.example.com, b7.example.com, etc. None of these are cached. Each generates a fresh DNS lookup with a fresh transaction ID. Each lookup is an opportunity to race forged responses.

With 65,536 possible transaction IDs and modern hardware generating forged packets, you can run through all possibilities in under 10 seconds per subdomain query. Query enough random subdomains in parallel and you poison the NS glue records for the entire zone in seconds.

The resolver, now poisoned, directs all example.com queries to the attacker's nameserver — including queries for records it definitely had cached before, since the NS authority overrides.

The Birthday Paradox in Practice

The math behind cache poisoning success probability follows the birthday paradox. You're not trying to match one specific transaction ID (1 in 65,536). You're generating many forged responses per query, each with a different transaction ID.

With 16-bit transaction IDs, you need roughly 300 forged packets per query to get a 50% success rate (birthday attack approximation). Modern hardware can send millions of UDP packets per second. Each random subdomain query gives you a fresh attack window.

This is why 16-bit transaction IDs weren't enough. The entropy space was too small.

The Patch: Source Port Randomization

The fix shipped in July 2008 adds a second source of randomness: the UDP source port. Instead of always sending DNS queries from port 53, the resolver randomizes the source port for each query. Combined with the 16-bit transaction ID, the total entropy space jumps to 32 bits (16-bit port + 16-bit transaction ID = ~4 billion combinations).

Racing 4 billion combinations at network speed takes hours. The attack window for most queries is milliseconds. Poisoning became impractical.

Check if your resolver uses port randomization:

# DNSSec-Tools porttest
dig +short porttest.dns-oarc.net TXT

# Output should say "GREAT" or similar — indicates good randomization
# "POOR" means you're vulnerable

Or query the OARC test directly:

dig +short @your-resolver-ip porttest.dns-oarc.net TXT

dnspooq: The Return (2021)

Port randomization significantly raised the bar, but didn't make poisoning impossible. In January 2021, researchers published dnspooq (CVE-2020-25681 through CVE-2020-25687), a set of vulnerabilities in dnsmasq that enabled cache poisoning via:

  • Insufficient source port randomization in dnsmasq
  • A fragment-based attack that bypassed port randomization by exploiting IP fragmentation
  • Multiple simultaneous caches in certain dnsmasq configurations

dnsmasq runs on hundreds of millions of devices: home routers (DD-WRT, OpenWrt), Android devices, embedded systems. This wasn't an academic attack. Vendors shipped patches through late 2020 and into 2021. Many devices still haven't been updated.

The fragment attack was particularly interesting: oversized DNS responses trigger IP fragmentation. The second fragment (which contains the transaction ID) can be crafted separately from the first. Fragment ID (32 bits) + transaction ID (16 bits) with weak fragment ID generation gives far lower entropy than expected.

DNSSEC: The Real Fix

Source port randomization is a mitigation. DNSSEC is the actual solution.

With DNSSEC, every DNS record is cryptographically signed by the zone owner. A resolver with DNSSEC validation enabled won't accept a forged response — it can't, because the forged response won't have a valid signature.

Cache poisoning is structurally prevented when DNSSEC is deployed end-to-end: signed zone + validating resolver. An attacker can flood all the forged responses they want. Without the zone's private key, they can't produce valid RRSIG records.

We cover DNSSEC in detail in Lesson 05.

Checking Resolver Vulnerability

# Check port randomization via OARC
dig +short porttest.dns-oarc.net TXT @8.8.8.8

# Verify your local resolver
dig +short porttest.dns-oarc.net TXT @127.0.0.1

# Check DNSSEC validation on your resolver
dig +dnssec sigok.verteiltesysteme.net A
# RRSIG record in the response = DNSSEC validation working

dig +dnssec sigfail.verteiltesysteme.net A
# Should return SERVFAIL if validation is working

Key Takeaways

  • Cache poisoning injects forged DNS answers into a resolver's cache, affecting all users of that resolver
  • The Kaminsky attack bypassed the transaction ID defense by attacking NS records for an entire zone using random subdomains as an iteration mechanism
  • Source port randomization (16-bit port + 16-bit txid = ~4 billion combinations) made the attack impractical
  • dnspooq (2021) showed that poisoning is still possible on unpatched dnsmasq via IP fragmentation attacks
  • DNSSEC validation eliminates the threat entirely by requiring cryptographic signatures — no valid signature, no accepted response
  • Run the OARC porttest regularly against your resolvers, especially embedded/router-based ones

Further Reading

Up Next

Lesson 04 covers domain hijacking: how domains get stolen without touching the DNS protocol, and how registrar locks and registry locks actually work.