DNS Security & Abuse Intermediate 8 min read

DNS Cache Poisoning: How Forged Answers Enter Resolver Caches

How DNS cache poisoning works, why forged responses must match outstanding resolver transactions, and how modern DNS defenses reduce the risk.

Updated August 21, 2026

A recursive resolver doesn’t answer one client. It remembers what it learns and serves that memory to everyone who asks next. Caching is where DNS gets its scale, and it’s also why a single bad answer can matter so much. Poison the memory once and the resolver repeats the lie on its own.

That’s DNS cache poisoning. The classic version is a race, where an attacker tries to slip a forged response into the gap while a resolver waits for a real one.

A forged answer has to match an outstanding query

DNS over UDP opens no connection. The resolver sends a datagram to an authoritative server and waits for something that matches the transaction it has in flight. The DNS header carries a 16-bit transaction ID chosen by the requester, and the response has to echo it back, but modern acceptance rules demand considerably more than that.

RFC 5452 requires a response to match the outstanding query on all of the following before its data is accepted:

  • The question in the response matches an outstanding question
  • The transaction ID matches
  • The response comes from the address the query was sent to
  • The response arrives on the same local address and port the query left from

An off-path attacker, one who can’t see the real query go out, has to guess enough of those values to make a forgery look authentic.

DNS cache poisoning race showing a recursive resolver querying an authoritative server while an off-path attacker floods forged responses, only one of which could match the outstanding transaction ID, port, and question
Figure 1: Off-path poisoning is a race to produce a response matching every value the resolver expects, before the real answer arrives.

The race itself

Say a resolver needs www.example.com A. It queries the authoritative server, and the genuine response is now in flight. The attacker floods forged responses claiming to come from that server, each carrying something like:

www.example.com.    300    IN    A    192.0.2.66

A forgery only works if the resolver accepts it as the answer to that outstanding query. Wrong ID, wrong port, wrong source, wrong question, and it should be discarded. If a matching forgery arrives first, the false RRset gets cached. If the real answer lands first, the transaction closes, and later forgeries no longer match anything outstanding.

RFC reference
Response-matching requirements and source-port randomization guidance for resistance to forged answers are specified in RFC 5452.

Kaminsky made the race repeatable

Attacking a name directly used to carry a real cost. Lose the race for www.example.com, the genuine answer gets cached, and you wait for that RRset to expire before another upstream race is even available.

In 2008 Dan Kaminsky removed the wait. Instead of attacking the popular name, query one nobody has cached: a1.example.com, then a2.example.com, and onward. Each unique name misses the cache, forces a fresh upstream query, and opens a new race immediately. Losing costs almost nothing beyond moving to the next label. (Not literally unlimited: negative caching, resolver rate limits, and query coalescing all push back. But the attacker no longer waits on a TTL.)

The payload made it worse. Those forged responses didn’t have to carry a bogus address for the throwaway name. They could carry bogus delegation data in the authority section, with matching addresses alongside it, pointing the zone’s name servers at attacker infrastructure. Win once and the attacker can redirect later lookups for the entire zone through that resolver, for as long as the poisoned data survives in its cache.

Did you know?
The Kaminsky flaw (CVE-2008-1447) triggered a coordinated, industry-wide patch effort. Multiple vendors shipped fixes on a single day in July 2008, weeks before the full attack technique was presented publicly.

Source-port randomization widened the guess

The 16-bit transaction ID allows 65,536 values, which is a small search space for an attacker sending thousands of packets. Randomizing the UDP source port per query adds a second value the attacker has to guess, and that was the emergency mitigation the industry shipped in 2008. RFC 5452, published the following January, standardized it alongside unpredictable transaction IDs and strict response matching.

How much unpredictability this buys depends on the deployment. A resolver may be confined to a narrow port range, and network address translation can rewrite source ports on the way out. What protects the resolver is the combined uncertainty genuinely available to it, not a theoretical bit count. Port randomization also raises the cost of an attack rather than eliminating it.

Admission rules limit the blast radius

Kaminsky worked because a response can carry more than the record you asked for. Delegations bring NS records and glue addresses, and other answers can trigger additional-section processing. So which of that data is a resolver willing to keep?

Two separate mechanisms are at work. Relevance and bailiwick checks decide which authority and additional records are even eligible for caching, rejecting data about zones the responder has no business speaking for. Separately, RFC 2181’s ranking of data trustworthiness decides which data wins when a newly received record conflicts with something already cached. Neither authenticates a packet, and a well-formed forgery can still look thoroughly authoritative, but together they stop one response from rewriting whatever it likes.

Standards reference
DNS data credibility and conflict handling are defined in RFC 2181. Scope restrictions on accepting authority and additional data are covered in RFC 5452.

One poisoned cache serves everyone behind it

The damage scales with how widely a resolver is shared. Once false data is in the cache, every client within that cache’s scope can receive it, and none of them has to be present for the original forgery.

Case example
A shared recursive resolver accepts a forged A RRset for www.example.com and stores it. The next client asking that resolver for the same record gets the false address straight from cache. The attacker never has to win a second race while that entry stays usable.

The entry sticks until ordinary cache behavior clears it: expiry, eviction, an administrative flush, or a refresh path. There’s no single number for that lifetime, since resolver policies including stale serving all play a part. This is what separates poisoning from spoofing one reply to one endpoint.

On-path attackers don’t have to guess

Transaction IDs and port randomization defend against attackers who must predict the transaction. Someone who can observe the traffic simply reads the query name, ID, source port, destination, and timing off the wire. RFC 5452 says plainly that its defenses don’t protect against a party able to observe, modify, or inject traffic on the resolver’s path.

That turns the problem from prediction into authentication. Encrypted transports such as DNS over TLS or HTTPS protect a covered hop from both observation and tampering, depending on where the encryption ends, but they don’t authenticate ordinary unsigned DNS data across the full recursive path.

Encryption helps most against exactly the attacker randomization can’t stop, the one reading the query off the wire. But it only protects the hop it covers, and the recursive-to-authoritative leg, where much of this exposure sits, is today still mostly cleartext.

DNSSEC makes a matching forgery insufficient

A validating resolver runs the usual transaction checks and then verifies the data cryptographically. For a signed zone with an intact chain of trust, an attacker can’t mint a replacement RRset that validates without the signing key, so winning the UDP race stops being enough.

The test changes from “does this match the transaction?” to “does it match the transaction and validate under the trusted chain?” That’s a far stronger question, and it’s the subject of DNSSEC. The protection is also specific. DNSSEC offers no confidentiality and claims none against denial of service. It authenticates data that validates as secure, so a zone a resolver has proven to be unsigned gains nothing, and parent-side delegation records are themselves unsigned, meaning forged delegation data can still break resolution for a signed zone even when it can’t produce a validating answer.

Cookies and fragmentation narrow other surfaces

DNS Cookies (RFC 7873, updated by RFC 9018) add a lightweight transaction check. Client and server exchange cookie values that later messages carry, making blind forgery harder. The protection is deliberately modest, requires support at both ends, and does nothing once an attacker can observe the cookie.

Fragmentation is the other soft spot. Large UDP responses can fragment at the IP layer, and because individual fragments don’t each carry a complete DNS message, fragment handling has been abused to inject data into otherwise legitimate responses. RFC 9715 recommends avoiding IP fragmentation for DNS over UDP: keep response sizes below the point where fragmentation happens, have requestors drop fragmented UDP responses rather than reassembling them, and fall back to TCP instead.

RFC reference
RFC 9715 documents DNS-over-UDP fragmentation risks and techniques for avoiding IP fragmentation.

Summary

Cache poisoning succeeds when false data is accepted into a recursive resolver’s memory and reused as though it were legitimate. Classic off-path attacks race a forged response against the real one, and Kaminsky’s contribution was showing that fresh names make that race endlessly repeatable, with a delegation as the prize rather than a single hostname.

Nothing about the resolver’s job has changed. It still learns answers from upstream and remembers them for whoever asks next. What changed is how much has to line up before something is allowed into that memory. Transaction IDs and randomized ports make blind guessing expensive, admission rules limit what one response can touch, cookies and fragmentation guidance close specific gaps, encrypted transport denies an on-path attacker the values they would otherwise read off the wire, and DNSSEC authenticates the data itself.