Warning: this post is a walkthrough and contains spoilers.
One of the more fun challenges for me in Kringlecon 2018 was the Python escape challenge. You’re logged into a restricted Python shell
and had to find some way to execute the file i_escaped
inside of the user’s home directory. Attempting to exit Python via exit()
would log you
out of the system: you had to find a way to either invoke bash
or directly run i_escaped
from inside of Python. This was a practical
exercise to go with Mark Baggett’s https://www.youtube.com/watch?v=ZVx2Sxl3B9c Kringlecon talk- but at the time I didn’t
realize that and dove straight in, which resulted in me finding an alternative solution.
Escaping the sandbox
Normally running a program from inside Python would just be a few quick commands at the REPL:
import os
os.system("./i_escaped")
However, trying to run this immediately blocks you with the message Use of the command import is prohibited for this question.
One alternative
way we might get around this would be to try to use exec
, which can take in a string of code and run that. Unfortunately, trying to run exec
gives us a similar error message that it’s banned as well. Next on our list of tricks is eval
, which is a little more restricted in what it can run:
it only runs expressions, not statements, but we can still pass in some strings and run them.
eval("exec")
still gives us a message saying that exec
is disallowed, but nothing about eval
being banned. Apparently whatever is restricting us is
reading our input as a string and blocking keywords from a banned list. We can sneak past it, however: the restriction function isn’t clever enough
to understand what e = eval("ex" + "ec")
means, since it’s doing a simple string comparison. We’ve now renamed exec
to e
,
which the sandbox has no problem with.
With exec
in hand, we can rewrite our original code and escape the sandbox.
e = eval("ex" + "ec")
e("imp" + "ort os")
e("o" + "s.sys" + "tem('./i_escaped')")
I believe this is the intended solution. It’s not the one I came up with first, however:
Destroying the sandbox
When I first solved the problem, I only knew about eval
and not exec
, so I couldn’t assemble the full solution. So let’s suppose the sandbox writer
found out about this problem and patched it to ban eval
as well. What could we do then?
Earlier I made the assumption that there was some kind of banned words list that was being used to block Python commands that would be useful to escape.
We can call globals()
to see if something like that is in scope:
{'whitelist': [], '__builtins__': <module 'builtins' (built-in)>, '__name__': '__main__', '__do
c__': None, '__file__': '/bin/shell', 'sys': <module 'sys' (built-in)>, 'e': <built-in function
exec>, '__package__': None, 'code': <module 'code' from '/usr/lib/python3.5/code.py'>, 'restri
cted_terms': ['import', 'pty', 'open', 'exec', 'compile', 'os.system', 'subprocess.', 'reload',
'__builtins__', '__class__', '__mro__'], 'banner': [...]}
restricted_terms
is a suspicious sounding name, and it has several terms we were banned from using. What if we just got emptied it out?
restricted_terms = []
import os
os.system("./i_escaped")
We’re free to do anything we want at this point. At this point I watched the video to learn about exec
, compile
, and other useful things I could
have used to accomplish an escape- but attacking the implementation itself was satisfying in a completely different way.
I learned a lot from Kringlecon this year and I plan to do more writeups on some of the flags involved. Next time we’ll take a look at command injection with bash.