DNS cache poisoning is still one of the cleanest ways to send users to a server the attacker controls without touching the victim’s machine or the legitimate website. If you run recursive resolvers, inherit network gear you didn’t configure, or answer for DNS security in audits, this article covers how poisoning works, why randomization of query parameters is the main thing standing between your cache and a forged answer, and how to check that your randomization actually survives the trip through your network.
The short version is that a resolver accepts a response when it matches what the resolver expects. Every bit of unpredictability in that match makes a forged reply far less likely to land.
How a Forged Answer Ends Up in the Cache
A recursive resolver sends a query to an authoritative server and waits for the reply. If recursive and authoritative resolution is new to you, think of the resolver as a middleman whose cache serves thousands of clients. Poison that cache once and every client asking for the same name gets the attacker’s IP until the TTL runs out.
Over UDP, the resolver checks a small set of fields before it accepts a reply: the question name and type, the 16-bit transaction ID (TXID), and the destination port the reply arrives on. An off-path attacker can’t see the original query, so they flood the resolver with forged replies and hope one matches before the real answer arrives.
With only a 16-bit TXID, there are 65,536 possible values. That’s a small number. Thousands of spoofed packets per second can work through it within the race window, especially if the attacker can trigger the query themselves.
The 2008 Lesson: Why Randomization Matters for DNS Cache Poisoning
Before July 2008, the usual answer was “TTLs protect you.” An attacker only got one try per name until the cached record expired, and that could be hours. Dan Kaminsky’s disclosure (CVE-2008-1447) broke that assumption. Instead of racing for www.example.com, the attacker queries random names like a8f3.example.com that are never cached, so they get unlimited attempts. Each forged reply carries a malicious authority section that delegates the entire zone to the attacker’s nameserver.
The coordinated fix was source port randomization. If the resolver picks a random UDP source port for each query from a pool of roughly 60,000 ports, the attacker has to guess the port and the TXID together. That pushes the search space from 2^16 to around 2^32, about 4 billion combinations. It didn’t make poisoning impossible, but it moved a minutes-long attack to one needing weeks of sustained traffic, which a monitored network notices.
A third layer came later: 0x20 encoding, which randomizes the letter case of the query name (wWw.ExAmPlE.cOm) and requires the response to echo it back exactly. Unbound supports it with use-caps-for-id: yes. The extra entropy depends on how many letters the name contains, so short names gain little.
Myth: Randomization Solved This Problem for Good
A common belief is that cache poisoning is a 2008 problem and that patched resolvers are immune. They aren’t. In 2020, researchers published the SAD DNS attack, which used a side channel in ICMP rate limiting to find which source port a resolver had open. That reduced the problem back to guessing the 16-bit TXID. The attack hit major public resolvers until kernel and resolver changes went out.
Fragmentation-based attacks are another route. When a large UDP response gets fragmented, the second fragment carries no TXID or port at all. An attacker who can predict the IP ID can swap in a malicious fragment. Randomization is still required, but it’s a probability game, and side channels keep finding ways to shrink the odds.
The only cryptographic defense is DNSSEC validation on the resolver. Signing your zone does nothing for a user whose resolver doesn’t validate. See whether your organization should implement DNSSEC for the trade-offs.
A Familiar Scenario: Randomization Lost in Transit
An organization replaces its perimeter firewall over a weekend. The internal BIND 9 resolvers are patched, configured correctly, and pass their last review. Three months later, a pentester notices that outbound DNS queries leave the network with sequential source ports: 10241, 10242, 10243.
The resolver randomized the ports. The new firewall’s port address translation rewrote them in order. Nobody checked, because the resolver config hadn’t changed. This pattern has shown up since 2008 across many PAT implementations, and it’s the first thing an experienced DNS engineer checks after any network change on the resolver’s egress path.
How to Verify Your Resolver’s Randomization
A seasoned resolver operator checks what actually reaches the internet, not what the config file says. A 20-minute check covers it:
1. On a host outside your perimeter, or on the external interface, run tcpdump -n udp port 53 while triggering 50-100 queries for unique names through your resolver.
2. Look at the source ports. They should spread widely across the range with no visible sequence. If they’re consecutive, or stuck on one port, something is rewriting or pinning them.
3. Check the resolver config for old directives. A line like query-source address * port 53; in BIND removes source port randomization completely, and it still turns up in configs copied from pre-2008 guides.
4. On Windows Server DNS, confirm the socket pool is on. It defaults to 2,500 sockets and can go up to 10,000 with dnscmd /config /socketpoolsize.
5. Confirm the resolver isn’t open to the internet. An open resolver lets anyone trigger queries on demand, and that’s what an attacker needs to start the race.
Repeat this after every firewall, load balancer, or NAT change on the resolver’s path.
Common Mistakes Practitioners Make
The first mistake is trusting a forwarder without thinking it through. A small team of under 10 people forwarding everything to 1.1.1.1 or 8.8.8.8 hands randomization and validation to the upstream provider. That’s often a sensible choice. But the local forwarder still caches answers, and if it runs on a consumer router with weak randomization, the link between the forwarder and the upstream becomes the weak point.
The second is turning off 0x20 or DNSSEC validation after one broken domain. Some authoritative servers mishandle mixed-case queries, and some zones carry expired signatures. The fix is a per-domain exception, such as Unbound’s caps-exempt or a negative trust anchor in BIND, not disabling the protection for everything.
The third is treating long TTLs as a security control. Long TTLs mean a poisoned record lives longer once it lands, and they slow your recovery. Our guide to TTL settings and their security implications covers how to balance cache efficiency against exposure.
Some exceptions apply. Air-gapped networks with internal-only resolvers face little off-path spoofing risk, though an insider on the same segment can still race replies. Regulated environments under NIS2 or PCI DSS should document their randomization and validation settings as evidence, not just switch them on.
FAQ
Does DNS over HTTPS or DNS over TLS prevent cache poisoning?
They protect the path between the client and the resolver. They don’t protect the resolver’s own queries to authoritative servers, which mostly still run over plain UDP. Only DNSSEC validation covers that leg.
How can I tell if my cache has already been poisoned?
Compare answers from your resolver against direct queries to the authoritative server using dig @ns1.example.com. Unexpected NS records for major zones, or A records pointing at unfamiliar networks, are the key signs. Flush the cache, then look for how the bad answer got in.
Is source port randomization enough without DNSSEC?
It raises the cost of an attack a lot, but side channels like SAD DNS show the margin can shrink. Treat randomization as the baseline and DNSSEC validation as the real guarantee.
Summary
Cache poisoning works whenever an attacker can guess what a resolver expects, and randomization of the TXID, source port, and query name case is what makes that guess expensive. Turning these on in the resolver is only part of the job. Network devices quietly undo them, old config lines pin them, and side-channel research keeps chipping away at the margin.
Packet-capture your resolver’s outbound queries after every network change, keep validation on with per-domain exceptions instead of global opt-outs, and keep your resolvers closed to the internet. The capture takes 20 minutes, and it’s the only check that tells you whether your randomization actually reaches the internet.
