Mitigating DNS Cache Poisoning Attacks with iptables
14 July, 2008
It is well known that last week Dan Kaminsky publicized a cache poisoning exploit against DNS. The details of this exploit have not yet been released, but Dan will present a talk at the Blackhat Briefings next month that will clarify the technical specifics behind the vulnerability - and therefore how to exploit it. Even though Dan has yet to release the details, enough information has already been released to show that iptables may be able to mitigate the vulnerability with a single well-chosen rule in your iptables policy. Further, this technique can be used right now whenever a vulnerable DNS server is deployed on (or behind) a system running iptables. Although updates have been released in a coordinated effort for most platforms, not everyone patches their systems immediately, and in some cases it can be easier to execute a few iptables commands than to deploy such updates. Of course, patching the vulnerability should be the top priority, but the exact details of the attack haven't been disclosed so it is hard to gauge risk. That said, if Dan Kaminsky says to patch, then it's probably a good idea to do so. In the meantime, let's press on.The CERT advisory highlights three problems in the existing DNS infrastructure:
- Lack of sufficient randomness in the selection of source ports for DNS queries
- DNS transaction ID values that also exhibit insufficient randomness
- Multiple outstanding requests for the same resource record
How can iptables help? First, let's assume that source port predictability is a necessary prerequisite to poison a DNS cache by Dan's technique. So, if iptables can introduce additional randomness into the source port that bind uses - at least as far as any upstream DNS server can tell - for each DNS query then an attacker would not be able to rely on source port predictability. The iptables SNAT target supports the ability to randomize both TCP and UDP source ports (see the --random option) even if a userspace application chooses a specific source port. This applies to both locally generated and forwarded packets. Hence, if a piece of userland software (such as bind) uses predictable source ports, iptables can rectify this via the SNAT target subject to some connection tracking restrictions.
To illustrate this, we use nmap with its --source-port option to set the source port to 44444 for a UDP scan of port 53 on two different servers, and verify with a packet trace that the source port is indeed set to this value. We'll assume the IP's 22.2.2.2 and 33.3.3.3 simulate upstream DNS servers from a local DNS server on the system with IP 11.1.1.1:
# nmap --source-port 44444 -P0 -p 53 -sU 22.2.2.2 33.3.3.3
19:26:19.625637 IP 11.1.1.1.44444 > 22.2.2.2.53: [|domain]
19:26:19.625790 IP 11.1.1.1.44444 > 33.3.3.3.53: [|domain]
19:26:19.729520 IP 11.1.1.1.44444 > 33.3.3.3.53: [|domain]
19:26:20.626527 IP 11.1.1.1.44444 > 22.2.2.2.53: [|domain]
As you can see, the source port for each UDP datagram from 11.1.1.1 is set to 44444.
Now, to force the source port to be randomized when each packet is transmitted despite
setting it to 44444 from the nmap command line, let's use the SNAT target with the
--random option (note that it is assumed that the iptables policy is also using
connection tracking to allow expected replies through as well, etc.):
# iptables -t nat -I POSTROUTING 1 -p udp -s 11.1.1.1 --dport 53 -j SNAT --to 11.1.1.1 --random
By executing the same nmap command again, now we see that the SNAT target has changed
the source port on the outgoing UDP datagrams to 9374 (for 22.2.2.2) and 54743 (for
33.3.3.3):
# nmap --source-port 44444 -P0 -p 53 -sU 22.2.2.2 33.3.3.3
19:28:05.613637 IP 11.1.1.1.9374 > 22.2.2.2.53: [|domain]
19:28:05.613792 IP 11.1.1.1.54743 > 33.3.3.3.53: [|domain]
19:28:05.717536 IP 11.1.1.1.54743 > 33.3.3.3.53: [|domain]
19:28:06.617553 IP 11.1.1.1.9374 > 22.2.2.2.53: [|domain]
Hence, iptables can introduce randomness into source ports via a NAT operation
regardless of whether a userspace application requests a specific source port.
However, you may have noticed that nmap generates two packets for each scan of UDP/53, and you may have also noticed that the source port is 9374 for both packets sent to 22.2.2.2, and 54743 for both packets sent to 33.3.3.3. While the original source port of 44444 has definitely been randomized, it appears that this randomization is not taking place on a per-packet basis. What is really happening is that iptables has to use its connection tracking mechanism to map packets translated with the SNAT target to the correct socket, and this mapping is created from the srcIP/dstIP/sport/dport/protocol tuple along with a set of timers (see the nf_ct_udp_timeout and nf_ct_udp_timeout_stream variables in the kernel linux/net/netfilter/nf_conntrack_proto_udp.c file). So, if multiple requests are made to the same upstream DNS server within a 30 second time window, then the SNAT --random rule will map the source port for each request to the same (randomly assigned) source port - the initial tuples are the same after all.
At first glance this might seem to be a show stopper, but assuming that Dan's cache poisoning attack requires a process of querying different DNS servers - at least one of which is under the control of the attacker so that the source port behavior of the targeted server can be monitored - then the SNAT --random strategy would provide an effective defense. That is, the iptables state tracking code will assign a different random source port on (at least) a per-server basis even for rapid sets of DNS queries. Further, for queries to the same DNS server that are 30 seconds apart, the source port will change on a per-query basis as well.
This should be an effective defense against the attack, but we'll have to wait for Blackhat to be sure.
On another note, applications commonly do not bother requesting specific source ports for client sockets because they assume that the networking stack provided by the local kernel will assign a sensible source port. Or, they don't have to worry about the source port because they build in enough security at the application layer (unlike DNS implementations that don't sufficiently randomize transaction ID's) such that source port prediction does not give an advantage to the attacker. Still, those stacks that don't choose a random source port are not helping the state of network security, and the Kaminsky DNS attack is a perfect example of why. It turns out that the Linux kernel has only recently started randomizing UDP source ports as of 2.6.24.
Update 07/16/2008: Jon Hart has also written a blog post that illustrates using OpenBSD's pf firewall to implement a similar mitigation strategy. Hence, multiple pieces of firewall infrastructure can offer effective protection from the cache poisoning attack.