Imagine you're trying to get into a guarded door. The moment you open that door, a guard sees you and, identifying you as unauthorized, immediately kicks you out. But, suppose that an authorized person opens the door and props it open, and the guard will only verify the identity of the person walking through every 10 minutes or so, instead of continuously. They assume that an authorized person is using the door during that 10-minute window because they already authenticated the first person who opened it and propped it open.
Of course, this wouldn't happen in the real world (at least, I sure hope not), but the principle is often seen even in sophisticated industry-standard network access control systems. Instead of people, we're talking about packets on the network. As we learned from our fingerprinting exercise, the fine details of how a packet is formed betrays a particular source system. These details make them handy indicators of a source. It quacks like a duck and it walks like a duck, so it is a duck and definitely not a guy in a duck costume.
NACs employing this kind of fingerprinting technique will conduct an initial evaluation, and then assume the subsequent packets match the signature, just like our guard who figures the door is being used by the good guy after they do their first check. The reason for this is simple: performance. Whether the follow-up checks are every few minutes or never will depend on the NAC and configuration.
We're going to introduce a tool called Scapy to demo this particular attack. As we progress through this book, you will see that Scapy could easily replace most of the tools that pen testers take for granted: port scanners, fingerprinters, spoofers, and so on. We're going to do a quick demo for our NAC bypass here, but we will be leveraging the power of Scapy in coming chapters.
Fabricating the handshake with Scapy and Python
Kali Linux 2018.1 has Scapy ready to go, but it's good to make sure you have all your dependencies in order. My copy of Kali didn't have the Python ECDSA cryptography installed, for example. We don't need it here, but I don't like to have alerts when I fire up Scapy. You can run this command before we get started:
# apt-get install graphviz imagemagick python-gnuplot python-pyx python-ecdsa
You can bring up the Scapy interpreter interface by simply commanding scapy
, but for this discussion, we'll be importing its power into a Python script.
Scapy is a sophisticated packet manipulation and crafting program. Scapy is a Python program, but Python plays an even bigger role in Scapy as the syntax and interpreter for Scapy's domain-specific language. What this means for the pen tester is a packet manipulator and forger with unmatched versatility because it allows you to literally write your own network tools, on the fly, with very few lines of code – and it leaves the interpretation up to you, instead of within the confines of what a tool author imagined.
What we're doing here is a crash course in scripting with Python and Scapy, so don't be intimidated. We will be covering Scapy and Python in detail later on in the book. We'll step through everything happening here in our NAC bypass scenario so that, when we fire up Scapy in the future, it will quickly make sense. If you're like me, you learn faster when you're shoved into the pool. That being said, don't neglect curling up with Scapy documentation and some hot cocoa. The documentation on Scapy is excellent.
As you know, we set up our captive portal listener and OS fingerprinter at 192.168.108.215
. Let's try to browse this address with an unmodified Firefox ESR in Kali and see what p0f picks up:
We can see in the very top line, representing the very first SYN packet received, that p0f has already identified us as a Linux client. Remember, p0f is looking at how the TCP packet is constructed, so we don't need to wait for any HTTP requests to divulge system information. Linux fingerprints are all over the TCP three-way handshake, before the browser has even established a connection to the site.
In our example, we want to emulate an iPad (specifically, one running iOS 9.3.2 to match our user-agent spoof from earlier). Putting on our hacker hat (the white one, please), we can put two and two together:
- p0f has a database of signatures (
p0f.fp
) that it references in order to fingerprint a source - Scapy allows us to construct TCP packets and, with a little scripting, we can tie together several Scapy lines into a single TCP three-way handshake utility
We now have a recipe for our spoofing attack. Now, Scapy lets you construct communications in its interpreter, using the same syntax as Python, but what we're going to do is fire up nano and put together a Python script that will import Scapy. We'll discuss what's happening here after we confirm the attack works:
#!/usr/bin/python
from scapy.all import *
CPIPADDRESS="192.168.108.215"
SOURCEP=random.randint(1024,65535)
ip=IP(dst=CPIPADDRESS, flags="DF", ttl=64)
tcpopt=[("MSS",1460), ("NOP",None), ("WScale",2), ("NOP",None), ("NOP",None), ("Timestamp",(123,0)), ("SAckOK",""), ("EOL",None)]
SYN=TCP(sport=SOURCEP, dport=80, flags="S", seq=1000, window=0xffff, options=tcpopt)
SYNACK=sr1(ip/SYN)
ACK=TCP(sport=SOURCEP, dport=80, flags="A", seq=SYNACK.ack+1, ack=SYNACK.seq+1, window=0xffff)
send(ip/ACK)
request="GET / HTTP/1.1\r\nHost: " + CPIPADDRESS + "\rMozilla/5.0 (iPad; CPU OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1 \r\n\r\n"
PUSH=TCP(sport=SOURCEP, dport=80, flags="PA", seq=1001, ack=0, window=0xffff)
send(ip/PUSH/request)
RST=TCP(sport=SOURCEP, dport=80, flags="R", seq=1001, ack=0, window=0xffff)
send(ip/RST)
Once I'm done typing this up in nano, I save it as a .py
file and chmod
it to allow execution. That's it – the attack is ready:
The iptables
outbound rule is set, and the script is ready to execute. Let it fly:
That's it; not very climactic on this end. But, let's take a look at the receiving end.
Voila! The OS fingerprinter is convinced that the packets were sent by an iOS device. When we scroll down, we can see the actual HTTP request with the user agent data. At this point, the NAC allows access and we can go back to doing our usual business. Don't forget to open up iptables
:
# iptables -F
So what happened here, exactly? Let's break it down:
CPIPADDRESS="192.168.108.215"
SOURCEP=random.randint(1024,65535)
We're declaring a variable for the captive portal IP address and the source port. The source port is a random integer between 1024
and 65535
so that an ephemeral port is used:
ip=IP(dst=CPIPADDRESS, flags="DF", ttl=64)
tcpopt=[("MSS",1460), ("NOP",None), ("WScale",2), ("NOP",None), ("NOP",None), ("Timestamp",(123,0)), ("SAckOK",""), ("EOL",None)]
SYN=TCP(sport=SOURCEP, dport=80, flags="S", seq=1000, window=0xffff, options=tcpopt)
SYNACK=sr1(ip/SYN)
Now we're defining the layers of the packets we will send. ip
is the IP layer of our packet with our captive portal as the destination, a don't-fragment flag set, and a TTL of 64
. Now, when Scapy is ready to send this particular packet, we'll simply reference ip
.
We define tcpopt
with the TCP options we'll be using. This is the meat and potatoes of the OS signature, so this is based on our signature research.
Next we declare SYN
, which is the TCP layer of our packet, defining our randomly chosen ephemeral port, the destination port 80
, the SYN
flag set, a sequence number, and a window size (also part of the signature). We set the TCP options with our just-defined tcpopt
.
Then, we send the SYN request with sr1
. However, sr1
means send a packet, and record 1 reply. The reply is then stored as SYNACK
:
ACK=TCP(sport=SOURCEP, dport=80, flags="A", seq=SYNACK.ack+1, ack=SYNACK.seq+1, window=0xffff)
send(ip/ACK)
We sent a SYN packet with sr1
, which told Scapy to record the reply – in other words, record the SYN-ACK that comes back from the server. That packet is now stored as SYNACK
. So, now we're constructing the third part of the handshake, our ACK. We use the same port information and switch the flag accordingly, and we take the sequence number from the SYN-ACK and increment it by one. Since we're just acknowledging the SYN-ACK and thus completing the handshake, we only send this packet without needing a reply, so we use the send
command instead of sr1
:
request="GET / HTTP/1.1\r\nHost: " + CPIPADDRESS + "\rMozilla/5.0 (iPad; CPU OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1 \r\n\r\n"
PUSH=TCP(sport=SOURCEP, dport=80, flags="PA", seq=1001, ack=0, window=0xffff)
send(ip/PUSH/request)
Now that the TCP session is established, we craft our GET
request for the HTTP server. We're constructing the payload and storing it as request
. Note the use of Python syntax to concatenate the target IP address and create returns and new lines. We construct the TCP layer with the PSH + ACK flag and an incremented sequence number. Finally, another send
command to send the packet using the same IP layer, the newly defined TCP layer called PUSH
, and the HTTP payload as request
:
RST=TCP(sport=SOURCEP, dport=80, flags="R", seq=1001, ack=0, window=0xffff)
send(ip/RST)
Finally, we tidy up, having completed our duty. We build a RST packet to tear down the TCP connection we just established, and send it with the send
command.
I hope I have whetted your appetite for Scapy and Python, because we will be taking these incredibly powerful tools to the next level later in this book.