A customer asked me to check for Cisco Discovery Protocol (CDP) based VLAN hopping on their LAN. It had been reported the year before and, while they hoped that it had been addressed, they wanted me to confirm that it had.
When pentesting it can often be the case that you are basically verifying the solutions to problems that some hacker from another company got to run riot with before.
Far from a burden, it gave me a chance to brush up a little bit. It got me to play with scapy a bit and so this post is basically so I can save my python code in-case I need it again, while sharing it in case it helps someone.
The easy tool for this has been Frogger for several years. It will conduct packet sniffing on an interface looking for CDP packets. Snarffle the VLAN IDs and some other information and then ask you exactly how you want to own a network.
I ran frogger and shock! No VLAN data and no CDP packets to play with. I optimistically increased the packet capture to 3 minutes and tried again. Nope…
Well I guess last years team had much more fun than me.
A thing to remember about Frogger is that if you are trying to run it inside a virtual machine then you might actually be getting false negatives here. Where a false negative is the absence of the vulnerability despite you doing almost the right thing to check for it.
The Readme.md for Frogger says this:
“Notes for VMware. VLAN hopping generally (not just this script) can have issues within VMware if running the VM within Windows with certain Intel drivers. The Intel drivers strip off the tags before it reaches the VM. Either use an external USB ethernet card such as a DLink USB 2.0 DUB-E100 (old model 10/100 not gigabit) or boot into Backtrack nativiely from a USB stick. Intel has published a registry fix, to work on some models: http://www.intel.com/support/network/sb/CS-005897.htm – adding “MonitorMode” 1 to the registry does resolve the issue.”
I am paranoid about false negatives so I always run around with a USB Ethernet Device. I did use this for my run of frogger and so I was pretty much certain it was not a false negative.
Checking for the VLAN IDs
Earlier in the engagement I had captured around 2 hours worth of network traffic into a pcap file. This was done from my host OS and not from within my Kali VM. This would therefore have no doubt about capturing all the layer-2 juiciness. Since it was already sitting around why not use python to parse the pcap file and tell me any VLAN ID’s it contained?
You could use a wireshark filter (and I show you the expression at the end of this post). But I am doing things pythony more an more so lets get to scapy.
Enter the VLAN ID hunting script:
from scapy.all import * from scapy.utils import * import sys if len(sys.argv)!=2: print sys.argv + " <pcap_file>" sys.exit(-1) # If we get here we have a file pcapfile = sys.argv # Loop through the file and check each packet pkts=rdpcap(pcapfile) for pkt in pkts: if pkt.haslayer(Dot1Q): print "VLAN ID: " + str(pkt[Dot1Q].vlan)
For full disclosure the above is heavily based on a stack overflow answer here:
All I did was give it a usage and command line argument for re-usability.
I ran this against my 2 hours worth of packets and found not a single VLAN ID. So absolutely on that day, on that part of the network, there was no evidence of last years vulnerability. Well done to the customer they fixed a thing!
I beg the audiences indulgence for a sidebar
There is an episode of Red Dwarf where Dave Lister gets sick with a radioactively mutated virus. He meets characters that represent both his confidence, and his paranoia. Right about now I was listening to my inner US game show host (representing my confidence). I was thinking: job done CornerPirate. Job done.
Then my paranoia spoke up. You haven’t tried the above script against a PCAP where you knew there was a VLAN id. What if it didn’t work?
Generating a PCAP with VLAN IDs
So lets make a pcap file which has some VLAN IDs in there. Back to python and scapy:
from scapy.all import * from scapy.utils import * import sys def usage(): print "\nUsage:" print "\t" + sys.argv + " <vlan id>" sys.exit(-1) if len(sys.argv)!=2: usage() if sys.argv.isdigit() == False: print "Specified VLAN ID is not a number" usage() # If we get here we have a vlan id to inject vlanid = int(sys.argv) # Craft a packet and send it sendp(Ether(dst='ff:ff:ff:ff:ff:ff', src='11:22:33:44:55:66')/Dot1Q(vlan=vlanid)/IP(dst='255.255.255.255', src='192.168.0.1')/ICMP())
I ran the above with a few different numbers while using Wireshark to capture the packets. Visually I could now see there were definitely VLAN ID’s to be had as shown below:
The expression used was “vlan.id” which means “show packets which have a VLAN ID”. My 2 hours worth of packets resulted in zero packets for the same filter. My confidence was now building.
Rerunning my “check-vlan.py” script now showed the same results as Wireshark:
So there you have it. I now know my script does what it intended to.