SANS Penetration Testing

Mission Impossible? Thwarting Cheating in an Advanced Pen Test Class CtF: The SANS SEC660 Experience

[Editor's Note: SANS course on advanced pen testing (SEC660) teaches a lot of great, in-depth topics, including exploit development, network manipulation (NAC bypass, Scapy packet crafting, man-in-the-middle attacks, and more), and Python for pen testers with tons of hands-on exercises. The whole class culminates in a full-day, intense capture the flag event, where the winners earn a 660 challenge coin (which includes a cool cipher, natch).

But, when you teach a bunch of skills like that and hold a CtF on the last day, sometimes, a few students get a little too rambunctious in applying their new-found skills. At the risk of being indelicate, I'll come out and say it — they try to cheat. By using their Python skills along with their MiTM capabilities, they try to snarf flags from other teams attempting to send them to the score server. What's an enterprising course author to do? Well, Steve Sims has some clever things up his sleeve, turning the tables on such shenanigans using the concepts taught in the course with a little Python magic of his own.

I recommend you read through Steve Sims' script to see how he uses Python with Scapy to call Nmap, call the underlying OS, formulate HTTP requests, and more. Check it out! -Ed.]

By Stephen Sims

Here is a short blog article about an attack that students were attempting to pull off in some of the Capture the Flag (CtF) events as part of SANS SEC660: Advanced Penetration Testing, Exploits, and Ethical Hacking. To thwart their attempts, I wrote a python script. In this article, I'd like to review the skills and techniques students use to try to undermine the CtF, and tell you my technical approach to address it in class.

The Source

During Day 1 of class, which is focused mostly on network attacks, we spend a lot of time looking at various ways to pull off a Man-in-the-Middle (MitM) attack, and then what you can accomplish by having that position. We cover techniques such as attacking SSL, routers, switches, and Network Access Control (NAC) solutions. During Day 3 of class, we spend a lot of time on Python, and various Python-based tools such as Scapy (by Philippe Biondi) and the Sulley Fuzzing Framework (by Pedram Amini / Aaron Portnoy / Ryan Sears).

The Attack

Armed with this information taught in class, every so often a CtF team attempts to steal key submissions from other teams. Now, one could certainly argue that there is technically no cheating in a CtF; however, this does not mean it should be really easy to pull the attack off. To score in the SEC660 CtF, SHA-1 hashes, which act as keys, are submitted into the scoring system by each team. If a hash/key matches a challenge, points are awarded to the team. Regardless of whether SSL or simple HTTP is being used as the transport protocol to the scoring server, the aforementioned teams were attempting to, and sometimes successfully, performing ARP cache poisoning and SSL stripping. This would allow the teams performing the attack to potentially read valid key submissions from other teams and get the points without completing the challenge. Ouch.

The Solution

The script you are about to read was written in about 90 minutes during a live CtF, so please forgive the stylistic issues and cut corners, such as not putting in the full paths to binaries when using the system() function. One of the solutions I designed to thwart this type of attack, and note that I am only sharing just one of them, was to create a script that would make a lot of noise on the wire. The script is not well-commented (again with the quick turnaround during the game), but it's easy to read as it's in Python. I decided to use Scapy together with Python to do the following:

  • Scan the student subnets to look for inactive IP addresses within the valid range assigned during class, using Nmap. This way it doesn't stand out as an IP address that is obviously part of the script.
  • Use one of these addresses very briefly and also use a random MAC address in the VMware OUI range.
  • Automatically configure my interface with these addresses and perform a valid TCP_HTTP session to the scoring server.
  • Submit a pseudo-random SHA-1 hash as a key submission and use a pseudo-random PHP session ID.
  • Loop through this script until terminated.

The bottom line here is that my script injects false flags into the network, so anyone looking to steal a flag will likely get a non-valid flag delivered by my script. Instead of stealing a valid flag from a legitimate student, they will have stolen a false flag from my script, netting them NOTHING, except some wasted time.

Getting an automated script like this working with Scapy, that shows no errors when sniffing with a tool like Wireshark, can sometimes be challenging. There are multiple ways to get it working. Feel free to read through the script and use it to improve your Scapy skills, or even better, improve it and send it to me at I will totally buy you a beer! Don't forget to change the interface listed in the script if necessary.

-Stephen Sims

p.s. Josh Wright, Jake Williams, and I will be teaching SEC 660 using SANS on-line training system, vLive, from March 4 through April 17. No travel is required, as you can take the class from the comfort of your home or office. We meet twice a week, and we'll be sharing our best tips and tricks for advanced pen testing. Details are here:

from scapy.all import *
from time import sleep
from hashlib import sha1
from random import random, sample, randint
import string
from os import system
import logging
print "\nPlease stand by while NMAP results are collected... This could take a minute...\n"
f = os.popen("nmap -n -PA -p0 10.10.75,76,77,78.1-254 | grep 'scan report for'") #Grab IP Addr from student range
z = []

for lines in f:
y = lines.split("\n") #Split \n from extra possible host addr's shorter than 3 digits.
x = [] # Empty list
x.append(y[0]) #Append the IP addr from y, and ignore the possible \n's
r = y[0] #Assign the list element (IP ADDR) from y to r
z.append(r[21:33]) #Grab only the IP ADDR from the NMAP scan results

print "Collected %d IP Addresses... Standby..." % len(z)
while True:
print "Spoofing process started..."
sp = RandNum(1025,65535) #Random number for ephemeral port assignment.
char_set = string.ascii_lowercase + string.digits #Random string for PHPSESSID
w = ''.join("10.10."+str(randint(75,78))+"."+str(randint(1,254)))

for x in z:
if w == x:
w = ''.join("10.10."+str(randint(75,78))+"."+str(randint(1,254)))

system("ifconfig eth1 down") #You may have to change interface number...
system("ifconfig eth1 hw ether " + str(RandMAC("00:0c:29:*:*:*")))
system("ifconfig eth1 " + w + " " + "netmask")
system("ifconfig eth1 up")
system("iptables -A OUTPUT -p tcp --destination-port 80 --tcp-flags RST RST -s " + str(w) + " -d -j DROP")
ah = os.popen("ifconfig eth1 | grep 00:0c:29") #Grab IP Addr
for lines in ah:
x = lines.split("\n")
y = []
ah = x[0]
ah = ah[-19:]
print "Using MAC Address: " + ah

p = IP(src=w,dst="") #Random IP from student subnets.
saveip = p[IP].src
print "Saved IP IS: " + str(saveip)
key = sha1(str(random())).hexdigest()
print "Using key: " + key
myseq = 1000
q= TCP(sport=sp, dport=80, flags="S", seq=myseq)
SYNACK = sr1(p/q)

my_seq = myseq+1
my_ack = SYNACK.seq+1
ACK = TCP(sport=SPORT2, dport = 80, flags="A", seq=my_seq, ack=my_ack)
derp = send(p/ACK)

ACK = TCP(sport=SPORT2, dport = 80, flags="PA", seq=my_seq, ack=my_ack)
b = ''.join(sample(char_set,26)) #Joining 26 random chars from char_set for SESSID.
spoof = "HTTP/1.1 Host:"+\
"User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv: "+\
"Gecko/2009102814 Ubuntu/8.10 (intrepid) Firefox/3.0.15"+\
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"+\
"Accept-Language: en-us,en;q=0.5"+\
"Accept-Encoding: gzip,deflate"+\
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7"+\
"Keep-Alive: 300"+\
"Connection: keep-alive"+\
"Cookie: PHPSESSID="

r = "GET /checkscore.php?key=" + key + spoof + b

getReq = sr1(p/ACK/r)

my_seq = myseq+507
ACK = TCP(sport=SPORT2, dport = 80, flags="FA", seq=my_seq, ack=my_ack)
derp = sr1(p/ACK)
ACK = TCP(sport=SPORT2, dport = 80, flags="A", seq=my_seq+1, ack=my_ack+1)

derp = send(p/ACK)
print "Successfully spoofed packet, no errors..."


Posted February 11, 2014 at 11:12 AM | Permalink | Reply

san tran

Hah, came across this today so i thought i would also quickly brush up on my scapy..
Here is my answer to your "noisy" code It's quite simple, I noticed your code use random mac address
system("ifconfig eth1 hw ether " str(RandMAC("00:0c:29:*:*:*"))) " str(ipsrc) " ''"> " raw[24:64]
if(packet.load.find("GET /checkscore.php?key=")!=-1):
## if this packet is already part of trust, just print it out
print fields
if collection.has_key(macsrc): #if macaddress has been observed previously, it is valid!
trust.append(macsrc) #append to trust array
print collection[macsrc] #print the previously observed fields
print fields #print the current fields
collection[macsrc]=fields #otherwise, add it to collection dictionary to observe it later.
sniff(filter="tcp port 80 and dst " SUBMITSERVER,prn=customAction)

Posted February 12, 2014 at 9:15 PM | Permalink | Reply

san tran

wow'' I wonder why half of my comment (in the middle) there did not work.. and the code looks horrendous so why don't we try again with pastebin:
And yes, all this code does (after you have execute arp poison) is to look for 2 submission with the same mac address to add the machine to "trust" list.

Posted February 12, 2014 at 9:30 PM | Permalink | Reply

Stephen Sims

Nice solution! Thanks. Definitely beer worthy.
I've modified it now to generate some MAC to IP pairings to a DB file and to limit the number of randomly generated pairings to a realistic number so that it's not as easy to determine which ones are real and which ones are fake. i.e. You'll see the same addresses submitting score requests. I've set the SHA-1 hash/key creation to a limited number. I've also got another script to reply to ARP's for addresses used by the script in case someone tries to do a scan to see if the host is alive.
Thanks again! Anymore good solutions?

Posted February 13, 2014 at 3:39 AM | Permalink | Reply

San Tran

Ahh sounds good, I am sure spending more time to write a script to defeat that is possible but i am sure your code is just to make it harder to cheat and people won't bother.
Another quick solution maybe response with a fake redirect response back to the client and see if the client follow the redirect, that way we would know if it is from a proper browser.

Posted April 9, 2015 at 10:47 PM | Permalink | Reply


Hmm is anyone else encountering problems with
the pictures on this blog loading? I'm trying to find out if
its a problem on my end or if it's the blog. Any responses would be greatly appreciated.

Post a Comment


* Indicates a required field.