
What Is DNS Spoofing and Cache Poisoning?
DNS answers are trusted almost blindly. When your browser asks for bank.example and gets back an IP address, it connects there without any further checks at the DNS layer. If an attacker can make that answer point somewhere else, every user of that resolver can be silently routed to a server the attacker controls. That's the idea behind DNS spoofing, and when the forged answer gets stored in a resolver's cache and served to everyone, it becomes cache poisoning. The broader post on securing DNS against attacks gives an overview of the whole threat landscape; this article goes deep on this one class of attack.
We'll cover why classic DNS is vulnerable, how the major poisoning techniques work conceptually (from simple ID guessing to the Kaminsky attack and newer side-channel variants), how to detect poisoning, and the layered defenses that resolver operators, domain owners, and end users can each put in place.
Spoofing vs Cache Poisoning
The two terms are often used interchangeably, but they describe different scopes:
- DNS spoofing is the general act of delivering a forged DNS response — one that claims to come from a legitimate server but contains attacker-chosen data. It can target a single client or a resolver.
- Cache poisoning is spoofing aimed at a caching resolver. The resolver accepts the forged answer, stores it, and serves it to every client that asks until the TTL expires. One successful forgery affects thousands of users.
A related but different problem is DNS hijacking, where the attacker changes DNS settings or records directly — for example, on a router or in a registrar account — rather than forging responses in flight.
Why Classic DNS Is Vulnerable
Standard DNS queries travel over UDP, which has no handshake. A resolver sends a query and accepts the first response that looks like it matches. "Looks like it matches" historically meant checking just a few fields:
- The source IP and port of the response match the server the query was sent to.
- The destination port matches the port the resolver sent the query from.
- The 16-bit transaction ID matches the query's ID.
- The question section matches what was asked.
There's no cryptographic proof that the response came from the real server. Anyone who can send a packet with a spoofed source IP — which many networks still allow — and guess the transaction ID and port can, in principle, have their response accepted. The attacker just needs to get it there before the real answer arrives.
A 16-bit ID gives only 65,536 possibilities, and early resolvers sent all queries from a single fixed port. That combination made blind guessing feasible.
How Poisoning Attacks Work
Off-path guessing (the classic race)
An off-path attacker can't see the resolver's traffic, so they have to guess. Conceptually, the attack is a race: trigger the resolver to look up a name, then flood it with forged responses carrying different transaction IDs, hoping one matches before the legitimate authoritative server responds.
The limitation of the original version was that the attacker only got one try per TTL. If the resolver already had a cached answer for the target name, it wouldn't send a query, so there was nothing to race. And if the attacker lost the race, the real answer was cached and they had to wait.
The Kaminsky attack (2008)
Security researcher Dan Kaminsky showed how to remove that one-try limit. Instead of racing for the target name directly, the attacker triggers queries for random, non-existent subdomains of the target domain — names that can't be in the cache, so every one forces a fresh query and a fresh race.
The forged responses don't just answer the random name. They include an authority section claiming that the target domain's nameserver is an attacker-controlled host, plus glue pointing that nameserver at an attacker IP. Because that delegation information is "in bailiwick" — it's about the same domain the query was for — resolvers of the time would accept and cache it. Win one race out of many and the attacker effectively owns the entire domain in that resolver's cache.
The disclosure triggered a coordinated, multi-vendor patch in 2008. The main fix is described below as source port randomization.
Side-channel and fragmentation attacks
Once port randomization was widespread, researchers looked for ways to learn or bypass the randomness instead of guessing it:
- SAD DNS (2020) showed that ICMP rate-limiting behavior in operating systems could leak which UDP ports a resolver had open, letting an attacker narrow the port search dramatically before guessing the transaction ID. Linux and major resolvers shipped mitigations.
- Fragmentation attacks exploit large UDP responses that get split into IP fragments. The transaction ID and port are in the first fragment, but the records are partly in later ones, so an attacker who can inject a forged second fragment can alter the answer without guessing anything. This is one reason the DNS community moved to a default EDNS buffer size of 1232 bytes during DNS Flag Day 2020 — small enough to avoid fragmentation on almost every network path. The post on EDNS explains the buffer size setting.
On-path spoofing
An on-path attacker doesn't need to guess at all: if they can see the query, they can read the ID and port and reply instantly. This includes attackers on the same public Wi-Fi using ARP spoofing, rogue DHCP servers that hand out a malicious resolver, compromised routers, and malicious network operators. This threat is about the last mile, between your device and the resolver, and the defense is different — encryption, covered below.
Detecting Spoofing and Poisoning
Poisoning is designed to be invisible, but there are signals you can watch for.
Compare answers across independent resolvers
If one resolver returns an IP for an important domain that differs from what several independent resolvers return, that's worth investigating. CDNs and GeoDNS legitimately return different IPs in different places, so compare against expectations, not just against each other:
for r in 1.1.1.1 8.8.8.8 9.9.9.9; do
printf "%-10s " "$r"
dig @"$r" www.example.com A +short | sort | tr '\n' ' '
echo
done
# Then ask the authoritative server directly — the ground truth
dig @"$(dig example.com NS +short | head -n 1)" www.example.com A +norecurse +short
The loop prints each public resolver's answer on one line, and the final query asks one of the domain's own nameservers, bypassing all caches. If a resolver's answer doesn't match the authoritative one (and the domain doesn't use location-based answers), that resolver's cache is suspect.
Check DNSSEC validation status
For signed domains, a validating resolver sets the AD (Authenticated Data) flag when the answer's signatures check out:
dig @1.1.1.1 example.com A +dnssec
Look for flags: qr rd ra ad in the header. You can also confirm that your resolver actually rejects bad signatures by querying a domain that's intentionally misconfigured for testing:
dig dnssec-failed.org A
A validating resolver returns SERVFAIL for this name. If you get an IP address back, your resolver isn't validating, and DNSSEC can't protect you from poisoning. The delv tool, shipped with BIND, performs validation locally and explains why an answer passed or failed:
delv example.com A
delv prints ; fully validated for correctly signed answers, or the reason validation failed.
Watch resolver logs and metrics
On resolvers you operate, a burst of responses that don't match any outstanding query is a strong sign someone is attempting a blind spoofing attack. Unbound tracks these as unwanted replies and can react automatically — see the configuration below. Combined with a sudden spike in queries for random subdomains of one domain, it's a classic Kaminsky-style signature.
Defending Against Spoofing and Poisoning
No single control solves this. The modern approach is layered, and each layer is owned by a different party.
For resolver operators
- Run current resolver software. Modern BIND, Unbound, Knot Resolver, and PowerDNS Recursor randomize source ports and transaction IDs by default, enforce bailiwick rules, and include mitigations for side-channel attacks. Old, unpatched resolvers are the main target.
- Source port randomization. Randomizing the UDP source port for every query (standardized in RFC 5452) multiplies the attacker's search space from about 65 thousand to billions of combinations. Never pin
query-sourceto a fixed port. - 0x20 case randomization. Resolvers can randomize the upper and lower case of letters in the query name (
wWw.ExAmPlE.cOm). Authoritative servers echo the question back exactly, so a forged response must also guess the casing. - DNSSEC validation. This is the only defense that makes forged data cryptographically detectable regardless of how it arrived. Turn it on.
- Don't be an open resolver. Restrict recursion to your own clients so outside attackers can't trigger queries on demand.
- DNS cookies. RFC 7873 cookies let resolvers and servers recognize each other's responses, making off-path forgery much harder where both sides support them.
Here's how these look in Unbound:
# /etc/unbound/unbound.conf.d/hardening.conf
server:
# Only answer our own networks
access-control: 0.0.0.0/0 refuse
access-control: 192.0.2.0/24 allow
access-control: 127.0.0.0/8 allow
# DNSSEC validation
auto-trust-anchor-file: "/var/lib/unbound/root.key"
harden-dnssec-stripped: yes
# Anti-spoofing hardening
use-caps-for-id: yes
harden-glue: yes
harden-referral-path: yes
edns-buffer-size: 1232
# Flush the cache if too many unmatched replies arrive
unwanted-reply-threshold: 10000000
use-caps-for-id enables 0x20 case randomization, harden-glue rejects out-of-bailiwick glue, harden-dnssec-stripped rejects answers that should be signed but arrive without signatures, and unwanted-reply-threshold makes Unbound clear its cache as a defensive measure if it receives a suspicious volume of unexpected responses. The trust anchor path varies by distribution. Run unbound-checkconf before reloading.
The BIND equivalent for validation and recursion control:
// named.conf options excerpt
acl "trusted" { 192.0.2.0/24; localhost; localnets; };
options {
recursion yes;
allow-recursion { trusted; };
allow-query-cache { trusted; };
dnssec-validation auto;
edns-udp-size 1232;
};
dnssec-validation auto uses BIND's built-in root trust anchor and keeps it updated, while the ACL limits recursion to your own networks. Check it with named-checkconf before reloading.
For domain owners
You can't control which resolvers your visitors use, but you can make forged answers about your domain detectable: sign your zone with DNSSEC. Once your zone is signed and the DS record is published at the registrar, every validating resolver will reject forged records for your names, no matter how cleverly they were injected. The guide on DNSSEC and whether to enable it covers the setup and its risks.
HTTPS adds a second safety net: even if a user is directed to the wrong IP, the attacker can't present a valid certificate for your domain without also compromising a certificate authority. Strict HSTS keeps browsers from accepting a downgrade to plain HTTP. These don't prevent poisoning, but they limit what an attacker can do with it.
For end users and organizations
The last mile — your device to your resolver — is where on-path spoofing happens, and it's protected by encryption. DNS over HTTPS or DNS over TLS to a trusted, validating resolver means nobody on the local network can read or forge your queries. On networks you manage, also enable DHCP snooping and dynamic ARP inspection on switches to block rogue DHCP servers and ARP spoofing.
Summary of Defenses by Attack Type
| Attack | What it exploits | Main defenses |
|---|---|---|
| Blind ID guessing | 16-bit transaction ID | Source port randomization, 0x20, DNSSEC |
| Kaminsky-style | Accepting forged delegations | Bailiwick checks, port randomization, DNSSEC |
| Side-channel port inference | OS behavior leaking open ports | Patched OS and resolver, DNS cookies, DNSSEC |
| Fragmentation injection | Large fragmented UDP responses | EDNS buffer of 1232, TCP fallback, DNSSEC |
| On-path forgery | Visibility of plaintext queries | DoH or DoT to a trusted resolver, network hardening |
DNSSEC appears in almost every row because it's the only control that validates the data itself rather than making forgery harder to deliver.
DNS Spoofing and Cache Poisoning FAQ
DNS spoofing is delivering any forged DNS response. Cache poisoning is spoofing a caching resolver so it stores the forged answer and serves it to all its users until the TTL expires.
Yes, though it's much harder than before 2008. Side-channel and fragmentation techniques have shown that randomization alone isn't enough, which is why DNSSEC validation and patched resolver software remain important.
For signed domains queried through a validating resolver, yes — forged records fail signature checks and are rejected. It doesn't protect unsigned domains or users whose resolver doesn't validate.
It stops spoofing between your device and your resolver. It doesn't protect the resolver's own queries to authoritative servers, so the resolver still needs randomization and DNSSEC validation to avoid being poisoned.
Query dnssec-failed.org through it. A validating resolver returns SERVFAIL; a non-validating one returns an IP address. You can also look for the ad flag on answers for signed domains.
Partly. A poisoned answer can send you to the wrong server, but that server can't present a valid certificate for the real domain, so your browser shows a warning. Never click through certificate warnings on sensitive sites.
Disclosed in 2008, it let attackers poison an entire domain by forcing queries for random subdomains and racing forged responses that included a malicious delegation. Vendors responded with a coordinated patch that introduced source port randomization.
On resolvers you run, flush the affected name or the whole cache, for example with rndc flushname or unbound-control flush_zone. On your own device, flush the operating system and browser caches. Then fix the underlying weakness so it can't recur.
Conclusion
DNS spoofing and cache poisoning exploit a basic weakness of the original protocol: resolvers accept an unauthenticated UDP packet as long as a few fields match. Over the years, attackers have found increasingly creative ways to win that bet, from simple ID guessing to Kaminsky's random-subdomain technique to side channels that leak a resolver's ports. Each wave has been met with mitigations — randomized ports and IDs, case randomization, bailiwick enforcement, smaller EDNS buffers — that make forgery harder to deliver.
The durable answer, though, is to stop relying on unpredictability alone. DNSSEC makes forged data detectable, encrypted transports like DoH and DoT close the last-mile gap, and well-maintained resolvers that only serve their own clients remove the easy targets. If you run a resolver, harden and validate. If you own a domain, sign it. And if you're just a user, send your queries over an encrypted connection to a validating resolver you trust.
These references cover the attacks and defenses in more depth:
- RFC 5452: Measures for Making DNS More Resilient against Forged Answers — the standard for transaction ID and source port randomization.
- RFC 3833: Threat Analysis of the Domain Name System — the IETF's analysis of DNS threats, including spoofing and cache poisoning.
- RFC 7873: Domain Name System (DNS) Cookies — a lightweight mechanism to protect against off-path forgery.
- Cloudflare Learning Center: What is DNS cache poisoning? — an accessible explanation of poisoning and DNSSEC as a defense.
- Unbound Documentation: unbound.docs.nlnetlabs.nl — reference for hardening options such as use-caps-for-id and harden-glue.


