
What is DNS spoofing (cache poisoning) and how to prevent it?
DNS spoofing is any attack that makes a device or resolver accept a forged DNS answer, and cache poisoning is the most damaging form of it: the attacker gets a recursive resolver to store a fake record, so every user of that resolver is sent to the wrong IP address until the cached entry expires. You prevent it by running resolvers that randomise source ports and query IDs, keeping resolver software patched, enabling DNSSEC validation on your resolvers and signing your own zones, restricting recursion to your own clients, and protecting the network path between clients and resolvers, for example with encrypted DNS.
The attack works because classic DNS answers are unsigned and usually travel over UDP, so a resolver can't easily tell a genuine reply from a well-crafted forgery. In this article you'll learn how a resolver decides whether to accept an answer, how poisoning attacks have evolved from simple guessing to the Kaminsky attack and side-channel techniques, how to tell whether your resolver is well protected, and the configuration that hardens BIND and Unbound against spoofing.
Spoofing vs Cache Poisoning
People use the terms loosely, so it's worth separating them:
- DNS spoofing is the general idea of supplying a fake DNS answer. It can target a single device, for example by answering its query faster than the real resolver on a hostile Wi-Fi network.
- DNS cache poisoning is spoofing aimed at a caching resolver. Because the resolver stores the forged record and hands it to everyone who asks, a single successful attack affects many users at once and lasts for the forged record's TTL.
Both are different from DNS hijacking, where the attacker changes which resolver you use or alters the real records at the DNS provider. Hijacking doesn't need to forge packets, because the attacker controls part of the system that's supposed to give the right answer.
How a Resolver Accepts an Answer
When a recursive resolver needs to look up a name, it sends a query over UDP to an authoritative nameserver. When a reply arrives, the resolver accepts it if it matches the outstanding query on these points:
- Source and destination addresses: The reply comes from the IP address the query was sent to and arrives at the address it was sent from.
- Destination port: The reply arrives on the UDP source port the resolver used for the query.
- Query ID: The 16-bit transaction ID in the reply matches the one in the query.
- Question: The question section matches the name, type and class that were asked.
An off-path attacker can't see the query, but can send forged packets that claim to come from the authoritative server's IP address. To be accepted, a forged reply has to guess the port and query ID correctly, and it has to arrive before the genuine reply does. Whoever's answer arrives first and matches wins.
Why Early Resolvers Were Easy Targets
Older resolvers sent every query from the same source port. That left only the 16-bit query ID, 65,536 possible values, for an attacker to guess. Some early implementations even used predictable, sequential IDs. With a fast enough flood of forged replies, guessing the right ID was realistic.
The Kaminsky Attack
In 2008, security researcher Dan Kaminsky described a way to make poisoning far more practical. Before then, a failed attempt meant waiting for the real record to expire before trying again, because the resolver would cache the genuine answer. Kaminsky's insight was to avoid that wait entirely.
At a high level, the technique worked like this:
- The attacker causes the resolver to look up a random, non-existent subdomain of the target, such as
q8f3x.example.com. Because the name is new, it's never in the cache, so the resolver must ask the authoritative server. - The attacker floods the resolver with forged replies for that query. Instead of answering only the random name, the forged replies include referral data claiming that the nameserver for
example.comis at an attacker-controlled IP address. - If a forged reply wins the race, the resolver caches the fake referral. From then on, it sends every query for the domain to the attacker's server.
- If the attempt fails, the attacker immediately tries again with a different random name. There's no TTL to wait out.
The disclosure led to a coordinated patch across almost all DNS software. The main fix was source port randomisation, which forces the attacker to guess the port as well as the query ID. With tens of thousands of possible ports, the search space grows from about 65 thousand combinations to billions, which makes blind guessing impractical in most conditions.
Later Attacks: Side Channels and Fragmentation
Port randomisation raised the bar, but researchers have since found ways to reduce the guesswork again.
- Side-channel port inference: In 2020, the SAD DNS research showed that some operating systems leaked which UDP ports were open through the way they rate-limited ICMP error messages. That let an attacker narrow down the resolver's source port and then guess the query ID. Operating system vendors changed their ICMP rate limiting in response, and resolver vendors added mitigations.
- IP fragmentation: If a large DNS response is split into fragments, an attacker can try to replace the second fragment, which contains no port or query ID, with a forged one. Reducing the EDNS buffer size so responses don't fragment, as promoted by DNS Flag Day 2020 with a recommended 1232 bytes, removes much of this risk.
- Weak bailiwick checks: A resolver that accepts records in a reply for names unrelated to the question can be tricked into caching data the attacker shouldn't control. Modern resolvers only accept records that are "in bailiwick", meaning within the zone the queried server is responsible for.
The lesson from all of these is the same. Unsigned DNS relies on the attacker being unable to guess a few numbers, and every leak of those numbers reopens the door. Cryptographic validation closes it properly.
What a Successful Poisoning Attack Does
Once a resolver has cached a forged record, everyone who uses it is affected until the record expires. The consequences depend on what the attacker poisons:
- Web traffic can be sent to a phishing copy of a site. HTTPS helps here, because the attacker normally can't present a valid certificate, but users sometimes click through warnings and some software doesn't validate certificates properly.
- Email can be redirected by poisoning MX records or the addresses of mail servers. Server-to-server SMTP often doesn't enforce certificate validation, so this is a serious risk.
- Software updates can be pointed at malicious servers if update clients don't verify signatures.
- Certificate issuance could, in principle, be affected if an attacker poisons the resolver a certificate authority uses for domain validation. CAs mitigate this by validating from multiple network locations.
How to Prevent Cache Poisoning
Defence comes in layers. Some apply to people who run resolvers, and some to people who run domains.
1. Run Modern, Patched Resolver Software
Current versions of BIND, Unbound, Knot Resolver and PowerDNS Recursor all randomise source ports and query IDs and enforce bailiwick rules by default. Running an old resolver, or an embedded one in an ageing router or appliance, is the single biggest risk. Keep resolver software and the operating system up to date.
Check the version you're running:
named -v
unbound -V | head -n 1
2. Enable DNSSEC Validation on Your Resolvers
DNSSEC lets a validating resolver check signatures on records, so a forged answer for a signed zone is rejected however well the attacker guesses the port and ID. This is the only defence that defeats poisoning outright for signed zones.
In BIND, validation is controlled with:
options {
dnssec-validation auto;
};
auto uses the built-in root trust anchor and keeps it updated. In Unbound, enable the validator and the automatically managed root key:
server:
module-config: "validator iterator"
auto-trust-anchor-file: "/var/lib/unbound/root.key"
Many distributions ship Unbound with these settings already in place. Check that validation is working with a deliberately broken test domain. The test zones under dnssec-failed.org are signed with a broken chain for exactly this purpose:
dig @127.0.0.1 www.dnssec-failed.org A | grep status
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 48212
A validating resolver returns SERVFAIL. If you get an IP address instead, validation isn't enabled.
3. Sign Your Own Zones
Validation only protects lookups for zones that are signed. If you own a domain, enable DNSSEC at your DNS provider and publish the DS record at your registrar. That way, any resolver that validates can detect forged answers for your names. You can confirm a signed lookup by looking for the ad (authenticated data) flag:
dig @1.1.1.1 example.com A +dnssec | grep flags
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
4. Harden Resolver Configuration
Beyond the defaults, both major open-source resolvers offer extra protection. In Unbound:
server:
# Only accept glue and referral data that's in bailiwick
harden-glue: yes
# Treat a zone as bogus if DNSSEC data is expected but missing
harden-dnssec-stripped: yes
# Randomise letter case in queries (0x20 encoding) and check replies match
use-caps-for-id: yes
# Reduce the risk of fragmentation attacks
edns-buffer-size: 1232
# Take defensive action after many unwanted replies
unwanted-reply-threshold: 10000000
# Remove unsigned records from the additional section of signed answers
val-clean-additional: yes
# Don't reveal software details
hide-identity: yes
hide-version: yes
use-caps-for-id deserves a word of explanation. It randomises upper and lower case in the queried name, for example wWw.ExAmPlE.cOm. DNS names aren't case-sensitive, but a genuine server copies the question exactly, so an attacker also has to guess the case pattern. A small number of authoritative servers don't preserve case correctly, so test carefully before enabling it on a busy resolver.
In BIND, keep the EDNS buffer size at the modern default and limit who can use recursion:
acl trusted {
192.0.2.0/24;
2001:db8:100::/48;
localhost;
};
options {
recursion yes;
allow-recursion { trusted; };
allow-query-cache { trusted; };
dnssec-validation auto;
edns-udp-size 1232;
max-udp-size 1232;
};
5. Don't Run Open Resolvers
A resolver that accepts recursive queries from anyone on the internet is an easier poisoning target, because an attacker can trigger lookups at will. It can also be abused for amplification attacks. Restrict recursion to your own networks with allow-recursion in BIND or access-control in Unbound:
server:
access-control: 0.0.0.0/0 refuse
access-control: 127.0.0.0/8 allow
access-control: 192.0.2.0/24 allow
access-control: ::1 allow
access-control: 2001:db8:100::/48 allow
Test from a host outside your network that recursion is refused:
dig @203.0.113.53 example.org A
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 1771
;; WARNING: recursion requested but not available
6. Protect the Path to the Resolver
Spoofing doesn't always target the resolver's cache. On a local network, an attacker can answer a device's queries directly, sometimes after using ARP spoofing or a rogue DHCP server to put themselves in the path. Defences here include:
- Encrypted DNS between clients and resolvers, using DNS over HTTPS or DNS over TLS. An attacker on the local network can't read or forge encrypted responses.
- Network controls such as DHCP snooping and dynamic ARP inspection on managed switches.
- Validating locally, for example with a stub resolver that validates DNSSEC itself, so forged answers for signed zones are rejected even if the local network is hostile.
Monitoring for Signs of Poisoning
Poisoning is hard to see directly, but some signals are worth watching on resolvers you run:
- Large numbers of unsolicited or mismatched replies. Unbound counts these, and the
unwanted-reply-thresholdsetting exists precisely because a flood of them suggests an attack in progress. - DNSSEC validation failures for popular domains, which can indicate forged responses being rejected.
- Unexpected changes in answers for your own critical domains.
With unbound-control enabled, you can read the counters:
unbound-control stats_noreset | grep -E 'unwanted|num.answer.bogus|num.answer.secure'
num.answer.secure=184233
num.answer.bogus=12
unwanted.queries=0
unwanted.replies=37
A sudden rise in unwanted.replies or num.answer.bogus is worth investigating. For your own domains, a simple external check that compares answers from several public resolvers with the values you expect will catch both poisoning and hijacking:
#!/usr/bin/env bash
name="www.example.com"
expected="192.0.2.10"
for r in 1.1.1.1 8.8.8.8 9.9.9.9; do
got=$(dig @"$r" "$name" A +short | tail -n 1)
[ "$got" = "$expected" ] || echo "Mismatch from $r: $got"
done
FAQ: DNS Spoofing and Cache Poisoning
DNS spoofing is any attack that supplies a fake DNS answer. Cache poisoning is spoofing aimed at a caching resolver, so the forged record is stored and served to every user of that resolver until it expires.
Yes, although it's much harder than before 2008. Source port randomisation and other defences make blind guessing impractical, but side-channel and fragmentation techniques have shown that unsigned DNS can still be attacked in some conditions.
For signed zones queried through a validating resolver, yes. The resolver checks signatures and rejects forged answers. It doesn't help for unsigned zones, or if the resolver doesn't validate.
Partly. An attacker usually can't present a valid certificate for your domain, so browsers show a warning. But users may click through, and other protocols such as email may not validate certificates strictly, so DNS-level protection still matters.
Query a deliberately broken signed name such as www.dnssec-failed.org through the resolver. A validating resolver returns SERVFAIL, while a non-validating one returns an IP address.
DNS over HTTPS and DNS over TLS stop attackers on the local network from reading or forging answers between your device and the resolver. They don't protect the resolver's own lookups to authoritative servers, which is where DNSSEC helps.
On a resolver you run, flush the affected name or the whole cache, for example with rndc flushname in BIND or unbound-control flush in Unbound. Then find and fix the cause, such as outdated software or an open resolver.
Conclusion
DNS cache poisoning exploits the fact that classic DNS answers are unsigned and accepted on the basis of a few guessable values. From simple query ID guessing to the Kaminsky attack and later side-channel research, every technique has aimed to make a forged reply look genuine before the real one arrives. Port randomisation, bailiwick checks and smaller EDNS buffers have made that much harder, but they reduce the odds rather than remove them.
The strongest protection is cryptographic. Validate DNSSEC on every resolver you run, sign the zones you own, and keep resolver software current. Restrict recursion to your own clients, use encrypted DNS on untrusted networks, and watch your resolver statistics and critical records. Together, those steps make it very difficult for anyone to quietly redirect your users.


