Working in an assigned team of two or three students, you will explore how with knowledge of the structure of IP datagrams, you can write software that allows one computer to "spoof" another by sending out datagrams that appear to come from a different source. You'll see how this can cause security issues when the spoofed datagrams are targeted at a naively designed application such as our textbook's example UDP server.
Each team will use three computers I will provide that are running the Ubuntu distribution of Linux. (Each computer can be logged in under the mcs
account using the password 377
.) The three computers will not be connected to the campus network (or any other external network) during your experimentation. Instead, you will connect each using an Ethernet cable to an Ethernet switch that I will provide, forming a small local network. In the following description I refer to your team's computers as computers A, B, and C.
Connect the Ethernet switch to a power outlet.
Do the following on each computer:
Connect the computer to the campus network (either wireless or wired) but not yet to the Ethernet switch.
Download Wireshark and configure it to allow packet capture by doing the following four steps:
In a terminal window, execute
sudo apt-get install wireshark
In a terminal window, execute
sudo dpkg-reconfigure wireshark-common
and then select the "Yes" option in response to the question whether non-superusers should be able to capture packets.
In a terminal window, execute
sudo adduser mcs wireshark
Log out from the computer and log back in. Don't overlook this step.
Download and unzip spoofcode.zip, which contains the textbook's UDPServer.py and UDPClient.py programs plus the spoofUDP.py program I wrote for you to use for the spoofing.
On at least computer C, also download and unzip pyip-0.7.zip, which is a helper module used by spoofUDP.py
Disable the wireless network.
Edit the configuration for the wired network so that IPv4 is "link local only".
Use an Ethernet cable to connect the computer to the switch.
Wait until the display shows that the wired network is connected.
In a terminal window, run the ifconfig
command to verify the computer now has an IP address of the form 169.254.x.x. Make a note of each computer's address.
Run the Wireshark program and start a packet capture running capturing UDP packets on the eth0 interface. (You should leave this continuing to capture during all of the experimentation.)
Get the textbook's example running as expected on computers A and B:
On computer A, edit UDPClient.py so that in place of localhost
it has computer B's IP address as the server to connect to.
On computer B, edit UDPClient.py so that in place of localhost
it has computer A's IP address as the server to connect to.
In a terminal window on each of computers A and B, run
python UDPServer.py
In a terminal window on each of computers A and B, run
python UDPClient.py
Type in some lowercase text to each client and check not only that it is printed out uppercase, but also that this was done by using the UDPServer running on the other computer. You should check this by looking at the packets Wireshark captures; if everything is set up right, you should see each computer sending a request to the other and getting a reply back.
In order to have a context in which to try out spoofing, do the following steps:
On computer A, kill off the UDPServer, for example by typing control-C in its terminal window.
On computer B, run the UDPClient and enter a line of text. Using Wireshark, you should see that the UDP packet was sent to computer A, but that no reply came back because the server wasn't running. Continuing with Wireshark, make a note of the source port number on computer B that the client request came from. Leave the UDPClient on B running, waiting for a reply.
On computer A, start the UDPServer back up. Because the request from computer B has already been missed, you should still see the client on B hanging. Leave it that way -- our goal is to show how spoofing can allow computer C to generate a "ricochet" reply from A that goes to the waiting client on B, rather than back to C.
The fundamental tool you will use is the ability to open a "raw socket" that doesn't provide any network- or transport-layer services. Instead, when your program wants to send a packet, you provide the entire array of bytes that starts with the IP header, continues with the transport-layer header (such as UDP or TCP), and then continues on into the payload data. The raw socket passes that array of bytes directly to the link layer (Ethernet) for transmission. This gives you complete control over all of the header contents, including the ability to set a "source address" that isn't actually the address of the computer sending the packet.
Using information from the RFCs, you could figure out exactly what bytes to put where in order to construct IP and UDP headers. However, to make our life easier, the spoofUDP.py code I've given you makes use of a package Kenneth Jiang wrote call pyip which allows you to just specify what should go in each header field; it puts the data in the proper positions and formats.
On computer C, install the pyip module. In a terminal window, change directory into the pyip-0.7 directory that you unpacked from the zip file and then execute the following command:
sudo python setup.py install
Continuing on computer C, edit the file spoofUDP.py. Fill in as the source IP address the address of computer B; although the packet is actually going to come from computer C, it is going to claim to be from B. Likewise, fill in the source port number based on computer B's waiting UDPClient program (as earlier noted in Wireshark). For the destination IP address and port number, fill in the information about computer A's UDPServer.
After doing these edits, change your terminal window's directory to the one containing spoofUDP.py and run the following:
sudo python spoofUDP.py
With any luck, you'll see that the UDPClient on computer B is no longer waiting: it has printed out an upper-case version of the data sent by computer C's spoofUDP program (not an upper-case version of the data previously entered on computer B). Using Wireshark, you should be able to see that what happened is that computer A got a UDP packet that claimed to be from computer B, although actually it was from computer C. Therefore, computer A "replied" to computer B.
If this experiment didn't work, or you don't understand what happened, get whatever help you need before moving on to the next part.
Make one small change in the spoofUDP.py program: change the source port number so that instead of being the port that was used by computer B's UDPClient (which is no longer running), it is the port used by computer B's UDPServer. Leave the other parameters unchanged and once again run the following in a terminal window:
sudo python spoofUDP.py
Look for signs of what behavior results. One key place to look will be the Wireshark captures you left running. There may be other signs as well. If you don't see anything noteworthy, ask for help.
Your team should jointly author a report that answers the following questions, each worth 1 point:
What four key fields within the IP and UDP headers are relevant to your experiments?
Of those four, which are in the IP header and which in the UDP header?
Which of the four are relevant to getting a UDP message to a socket?
What are the other ones used for, which can be freely changed without disrupting the ability to reach the destination socket?
What did you observe on the various computers when computer C pretended to be the client waiting on computer B?
What did you observe on the various computers (and perhaps the Ethernet switch) when computer C pretended to be the server running on computer B?
Extra credit: Can you think of any defense against this sort of spoofing? Would it make any difference if the originating computer were on a separate network?
One student from each team should upload your report to Moodle.