Suspected DNS Cache Poisoning

Advanced DNS

Users on the network are being redirected to unexpected IP addresses when visiting known-good domains — the correct domain name resolves but returns a rogue IP that serves a phishing page or drops the connection. The resolver cache contains forged records injected by an attacker who won a source-port or transaction-ID prediction race, and cached records persist until their spoofed TTL expires.

Symptoms

  • A trusted domain resolves to an IP address that belongs to a foreign ASN or unknown hosting provider
  • Users report certificate warnings (mismatched CN/SAN) after navigating to a familiar site
  • whois or IP geolocation of the resolved IP does not match the expected CDN or hosting provider
  • The anomaly affects all clients on the same resolver but not clients using external resolvers
  • DNSSEC validation fails or is absent for an affected zone that should have DNSSEC
  • dig shows an unusually short TTL (e.g., 60 seconds) on a record that normally has a 3600s TTL

Possible Root Causes

  • Kaminsky-style cache poisoning: attacker floods the resolver with forged responses, predicting the transaction ID and source port before the legitimate response arrives — enabled by resolvers that do not randomize source ports
  • The resolver lacks DNSSEC validation, so forged records cannot be detected by signature mismatch
  • An on-path attacker (compromised router, BGP hijack, or ARP spoofing on LAN) intercepts DNS queries and injects forged responses
  • A compromised authoritative nameserver or upstream resolver in the recursive chain serves poisoned records
  • The resolver uses a predictable transaction ID space or a fixed source port (UDP 53 → 53), dramatically narrowing the attacker's brute-force space

Diagnosis Steps

Step 1 — Compare resolver output against authoritative source

# Query your local/corporate resolver
dig A suspicious-domain.com

# Query the authoritative nameserver directly (bypasses resolver cache)
dig NS suspicious-domain.com @8.8.8.8          # Find authoritative NS
dig A suspicious-domain.com @<authoritative-ns>  # Query authoritative directly

# Compare the two IPs — any discrepancy is a red flag

Step 2 — Verify the IP with Whois and geolocation

If the IP from your resolver differs from the authoritative source:

# Check IP ownership
whois <suspicious-ip>

# Verify ASN
dig -x <suspicious-ip>    # PTR lookup

Compare the registered org and ASN against what you expect (e.g., Fastly, Cloudflare, AWS). An IP in an unexpected country or ASN is strong evidence of a poisoned record.

Step 3 — Check TTL for signs of injected records

Legitimate CDN records often have TTLs of 60–300 seconds, so low TTL alone is not conclusive. However, combined with a mismatched IP:

# Check remaining TTL in resolver cache
dig +ttlunits A suspicious-domain.com @127.0.0.53

# Query the authoritative NS for its authoritative TTL
dig A suspicious-domain.com @<auth-ns> | grep -E "^suspicious-domain"

A poisoned record often has a very high TTL set by the attacker to persist as long as possible; or a low one to evade detection by expiring before forensics.

Step 4 — Verify DNSSEC validation status

# Check DNSSEC validation result
dig +dnssec A suspicious-domain.com @8.8.8.8
# Look for "ad" flag (Authenticated Data) in header

# Use a DNSSEC-validating resolver
dig +dnssec +cd A suspicious-domain.com @1.1.1.1
# +cd disables checking — if result differs, validation is failing

# Online tool: https://dnssec-analyzer.verisignlabs.com
drill -D suspicious-domain.com        # drill from ldns package

A SERVFAIL response from a DNSSEC-validating resolver when +cd succeeds is a clear indicator that the DNSSEC chain of trust is broken — consistent with poisoning of a signed zone.

Step 5 — Flush the resolver cache and re-query

# systemd-resolved
sudo resolvectl flush-caches

# macOS
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder

# Unbound
sudo unbound-control flush_zone suspicious-domain.com

# Windows
ipconfig /flushdns

# Re-query immediately after flush
dig A suspicious-domain.com

If the result changes after flushing, the previous cached record was different from what the resolver's upstream returns today — supporting the poisoning hypothesis.

Step 6 — Capture DNS traffic for forensic analysis

# tcpdump — capture all DNS traffic to/from your resolver
sudo tcpdump -i eth0 -w dns-capture.pcap port 53

# Analyze transaction IDs and source ports for patterns
# Kaminsky attack: attacker floods with many source ports and transaction IDs
# A burst of UDP packets to port 53 from unknown sources is suspicious
tshark -r dns-capture.pcap -Y dns -T fields \
  -e frame.time -e ip.src -e dns.id -e dns.qry.name

Solution

Immediate containment

# 1. Flush the poisoned cache entries
sudo resolvectl flush-caches         # systemd-resolved
sudo unbound-control flush_zone example.com   # Unbound

# 2. Switch all clients to DNSSEC-validating resolvers
# Cloudflare (validates DNSSEC)
nameserver 1.1.1.1
nameserver 1.0.0.1
# Google (validates DNSSEC)
nameserver 8.8.8.8
nameserver 8.8.4.4

Enable DNSSEC validation on your resolver

# Unbound — /etc/unbound/unbound.conf
server:
    module-config: "validator iterator"
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    harden-dnssec-stripped: yes

# Fetch the root trust anchor
sudo unbound-anchor -a /var/lib/unbound/root.key

# Restart
sudo systemctl restart unbound

# Verify validation works
dig +dnssec A google.com @127.0.0.1
# "ad" flag in header = validation successful

Enable source-port randomization (Unbound)

# /etc/unbound/unbound.conf
server:
    outgoing-port-permit: 1024-65535   # Randomize across full ephemeral range
    outgoing-num-tcp: 10
    use-caps-for-id: yes               # 0x20 encoding (additional entropy)

Sign your own zones with DNSSEC

If you operate authoritative DNS for your domain:

# Example with BIND 9
dnssec-keygen -a ECDSAP256SHA256 -n ZONE example.com
dnssec-signzone -A -3 $(head -c 1000 /dev/random | sha1sum | cut -b 1-16) \
  -N INCREMENT -o example.com -t db.example.com

After signing, publish the DS record at your registrar to complete the chain of trust.

Prevention

  • Enable DNSSEC validation on all recursive resolvers — this makes forged records cryptographically detectable regardless of how they entered the cache
  • Use DNS over HTTPS (DoH) or DNS over TLS (DoT) to encrypt resolver traffic, preventing on-path interception
  • Deploy a resolver that enforces source-port randomization and query-ID randomization (RFC 5452); Unbound, BIND 9.7+, and PowerDNS Recursor all support this
  • Sign all zones you control with DNSSEC and publish DS records at the registrar — even if you cannot validate all upstream zones, signed zones cannot be spoofed
  • Monitor resolver cache contents for anomalous records using periodic dig @<internal-resolver> checks against known-authoritative IPs

Related Protocols

Related Terms

More in DNS