Spoofing

Network Security Fall 2021
Due


The goals of this assignment are to:

  1. Learn how to sniff and inject network traffic using raw sockets
  2. Bypass IP address-based network authentication

IP Address Authentication

IP addresses have been widely used as network principals for authentication. It was common in the early years of UNIX to control access to network services by adding IP addresses to an allow list. (For example, see man hosts.allow.) IP addresses still see wide use as network principals in firewalls and malware block lists. However, as discussed during lecture, IP addresses are in many contexts not a reliable principal for several reasons, including churn, weak binding to identity, and – not least – the ability to forge packets with arbitrary source addresses using raw sockets.

Service Protocol

In this assignment, you will exploit a vulnerable network service using IP address authentication. The service, which you can find in Canvas as a02_ip_auth, implements a simple TCP protocol on port 10010 as follows:

\[ \begin{align*} C \rightarrow S &: \mathsf{u16}(|\mathsf{identifier}|) \| \mathsf{identifier} \\ S \rightarrow C &: \mathsf{u8}(\mathsf{authorized}) \| (\mathsf{u16}(|\mathsf{secret}|) \| \mathsf{secret} \| \mathsf{u16}(|\mathsf{nonce}|) \| \mathsf{nonce})? \\ \end{align*} \]

where \(\mathsf{identifier}\) is your @northeastern.edu email address encoded as a UTF-8 string, \(\mathsf{secret}\) and \(\mathsf{nonce}\) are UTF-8 encoded strings, \(|x|\) is the length of a byte array \(x\), \(\mathsf{u8}(\cdot)\) is a single byte, \(\mathsf{u16}(x)\) encodes an integer \(x\) as two big-endian bytes, and \(\|\) is concatenation.

After the client sends its ID, the server will check whether the sending IP address is authorized to use the service. If so, it will return a non-zero value for \(\mathsf{authorized}\) as well as a secret and nonce. Otherwise, it will return \(\mathsf{authorized}=0\) and no secret.

The only content your program should write to stdout is the following JSON object:

{
    "id": "{{identifier}}",
    "secret": "{{secret}}",
    "nonce": "{{nonce}}"
}

Whitespace formatting doesn’t matter, only that piping the output of your program validates as JSON of the above form. You can test whether you are doing this correctly by piping your solution to jq ..

Feel free to write whatever you like to stderr.

Bypassing Authentication

To bypass the service’s authentication procedure, you will need to forge packets with source addresses that are on the service’s allow list. To do so, you will need to construct and inject packets using raw sockets. You can do this with a multitude of libraries, though scapy is a good choice if you don’t have a reason to use another one.

Raw sockets allow user space programs to bypass the kernel network stack and directly read or write link-layer frames on specific interfaces connected to the local machine. Thus, when injecting packets you will need to build a valid Ethernet frame that contains a valid IPv4 datagram, which in turn contains a valid TCP segment, which in turn contains the correct payload.

Keep in mind that since you are injecting your forged request, you will also need to sniff the response! As in the injection case, you will sniff entire Ethernet frames off the wire and will need to decode each layer to correctly parse the payload.

Submission Instructions

Please follow these instructions exactly! The grader script will expect that your submission has the following directory structure and command-line interface.

Package your solution as a gzipped TAR archive. Your solution should expand to the following directory structure.

$ tree -F a02_spoofing
a02_spoofing
├── Dockerfile
└── src/

The source code to your solution should be contained in src/. Your Dockerfile should create a container image that runs your solution given the server’s socket address, associated MAC address, and a variable list of CIDR blocks representing the allow list as arguments. For instance, if the server is running on 10.0.0.10:1200 with MAC address 00:11:22:33:44:55 on interface eth0 and accepts requests from 10.0.0.0/24 and 10.0.1.0/24, then your container must print the secret when executed like so:

$ docker run -it --rm --cap-add net_raw ${solution_image} \
    172.17.0.101:1200 \         # Socket address
    00:11:22:33:44:55 \         # MAC address
    eth0 \                      # Interface
    10.0.0.0/24 10.0.1.0/24     # Allow list

Submit the solution archive to Canvas.