

Bypassing a Python sandbox by abusing code objects - JoachimS
http://pbiernat.blogspot.com/2014/09/bypassing-python-sandbox-by-abusing.html?m=1

======
bryanh
Building a truly safe Python sandbox (well, in CPython at least) is widely
considered a fool's errand [0]. However, a Python sandbox can be relatively
safely done in two ways:

1\. By completely reimplementing Python at a lower level and intercepting
system calls, like PyPy's sandbox feature [1].

2\. By utilizing other, more mature OS level constructs (IE: think
containerization like LXC (which is imperfect), stacking OS features like
seccomp/chroot/etc. or better, true virtualization like Xen).

Ideally, you'd combine them both and then run them on a "dumb box" which is
just a REST API with no other keys or system access. The key to designing
moderately secure sandboxes is just accepting that there are angles of attack
you haven't considered yet, so design in layers.

If you find yourself inspecting code to decide if it is safe, you are fighting
a losing battle... I'd love to hear of any success stories here though!

Great detailed write up of the pitfalls @op, thanks.

[0] [https://github.com/haypo/pysandbox/](https://github.com/haypo/pysandbox/)
or [https://lwn.net/Articles/574215/](https://lwn.net/Articles/574215/) [1]
[http://pypy.readthedocs.org/en/latest/sandbox.html](http://pypy.readthedocs.org/en/latest/sandbox.html)

~~~
Erwin
As far as I know, Zope's sandbox has not been broken:
[https://pypi.python.org/pypi/RestrictedPython](https://pypi.python.org/pypi/RestrictedPython)

The approach there is to rewrite your Python code to disallow any potentially
bad operation. E.g. your code cannot access identifiers starting with _ and
the list of builtins is restricted (no "open" by default obviously, but also
no "type"!)

Any sandbox code doing X.Y is rewritten to _your_getattr(X, "Y") which can
decide whether to allow access or not at runtime.

The main thing to be careful of, is not accidentally injecting any callables
like "file" into there, as just calling an object does not undergo a security
check (given that you might want to pass some data INTO your sandbox).

This does not limit resource usage however, i.e. you can still try to allocate
memory/use up CPU time.

~~~
dalke
Quoting from the bug page for "Zope sandbox escape via SecureModuleImporter
from Products/PageTemplates/ZRPythonExpr.py" at
[https://bugs.launchpad.net/zope2/+bug/1047318](https://bugs.launchpad.net/zope2/+bug/1047318)
:

> Restricted code doesn't actually protect against malicious users. It's only
> a best-effort attempt against introducing accidental security issues for
> semi-professional developers.

BTW, another attack vector (besides resource use) is to use code that causes
Python itself to crash. For example, "{[{}]}".format({"{}": 5}) will segfault
Python 3.3 (see
[http://bugs.python.org/issue17644](http://bugs.python.org/issue17644) ).

The harder question is, are any of the ways to segfault Python exploitable?

------
zzleeper
I'm wondering if it's possible to get a simpler/better attempt at sandboxing
by just inspecting the generated bytecode, instead of focusing on the .py text
file..

~~~
mrfusion
That's a really good point. I'm eager to hear a response.

It certainly seems like you could just ban a bunch of byte codes and easily
detect them. I must be missing something.

~~~
nl
Generally speaking it's the data associated with the calls that is risky, not
the bytecode itself.

The way the JVM works is by having specific permission checks at points within
the code, and they are enforced by the JVM. I think the CLR works in a similar
fashion.

You can run Python on top of the JVM and take advantage of this, but I think
the version of Python (JYthon) is pretty old.

------
orf
Very interesting read, I did something similar[1] on a Microsoft site. I
didn't think of creating a code object from scratch though, great idea.

I did a web app test once for an application created by IBM. They offered the
ability for administrators to run Python code in a 'restricted' sandbox to
manipulate data. The app was Java so it was run through Jython, and they
locked down all the usual suspects like open(), file() etc. But because it was
Jython you could just use the Java io.* classes and bypass all their
restrictions.

1\. [http://tomforb.es/breaking-out-of-secured-python-
environment...](http://tomforb.es/breaking-out-of-secured-python-environments)

------
thu
I was able to connect to any database from a multi-tenant ERP written in
Python. The idea was to get hold of the class of the connection object, and
re-instanciate it, while passing a new database name in parameter. That ERP
lets users (of different tenants) write Python code in various places. The
kind of code you could write was already restricted but not sufficiently. Now
all the dunder are forbidden, things like getattr() are also forbidden, which
makes the trick used in that blog post not possible. One of the funny thing I
had to do was to use lambdas to name intermediate values, because statements
were prohibited.

------
bobbyi_settv
> However, that requires accessing the "func_code" member, which is explicitly
> blocked.

But you've already shown that you can build the string "func_code" using your
xor code. So you can access the member using getattr/ setattr:

getattr(func, str_containing_func_code)

setattr(func, str_containing_func_code, new_code)

