securing a python execution environment...

C

Chris Withers

Hi All,

I'm trying to build a secure execution environment for bits of python
for two reasons:

- to allow users of the system to write scripts in python without
circumventing the application's security model

- to allow the system to have an environment where security is handled
without having to do explicit checks in every piece of example code.

This second point is better demonstrated by an example:

Bad:
Traceback (most recent call last):
File "<stdin>", line ?, in ?
AccessDenied: can't access 'someattr'

Good:
Traceback (most recent call last):
File "<stdin>", line ?, in ?
AccessDenied: can't access 'someattr'

Now, I think I can get a lot of this from Zope 3's security proxy
objects, however I need to find a way to limit the importing of modules
to, for example, prevent people importing the method that unwraps the
proxy objects ;-)

Have other people bumped into this problem?
What solutions do people recommend?

cheers,

Chris
 
G

Giles Brown

Hi All,

I'm trying to build a secure execution environment for bits of python
for two reasons:

- to allow users of the system to write scripts in python without
circumventing the application's security model

- to allow the system to have an environment where security is handled
without having to do explicit checks in every piece of example code.

This second point is better demonstrated by an example:

Bad:

Traceback (most recent call last):
File "<stdin>", line ?, in ?
AccessDenied: can't access 'someattr'

Good:

Traceback (most recent call last):
File "<stdin>", line ?, in ?
AccessDenied: can't access 'someattr'

Now, I think I can get a lot of this from Zope 3's security proxy
objects, however I need to find a way to limit the importing of modules
to, for example, prevent people importing the method that unwraps the
proxy objects ;-)

Have other people bumped into this problem?
What solutions do people recommend?

cheers,

Chris

Maybe this is of interest?
http://codespeak.net/pypy/dist/pypy/doc/sandbox.html
 
C

Chris Withers

Paul said:

Yeah, from this I'm pretty much set on:

http://pypi.python.org/pypi/RestrictedPython/

I know it's pretty bulletproof (I've been using it inderectly for
years!) but I do worry that it may be a little slow.

I guess some poking will tell...

If anyone's interested, I'll be continuing this discussion here:

http://mail.zope.org/pipermail/zope-dev/2007-November/030368.html

(this list is a little too high colume for me :-S)

cheers,

Chris
 
M

miller.paul.w

Here's some proof of concept code I wrote a while back for this very
purpose. What I do is use compiler.parse to take a code string and
turn it into an abstract syntax tree. Then, using a custom visitor
object that raises an exception if it comes across something it
doesn't like, I use compiler.ast.walk to walk the tree and check for
Bad Stuff (tm) such as:

* Importing a module not on the whitelist of safe modules.
* Accessing a name that begins with "__" (double underscore).
* Using the exec statement.
* Expecting the Spanish Inquisition.

Hehe, ok, just kidding about that last one. :)

If Bad Stuff(tm) is not detected, then I just compile it with the
built-in compile function and return the resulting code object.

Here it is:

---CUT HERE---

import compiler

# 'this' is the only module I was absolutely sure was safe. :p

allowed_imports = ['this', ]

class __Visitor (compiler.visitor.ExampleASTVisitor):

def visitImport (self, node):

# I have no idea why, but the 'names' attribute of an Import
or a From
# node always seem to be of the form ('name', None). (Hence
the
# 'name[0]' in the following list comprehension rather than
simply
# 'name'. Someone(tm) should definitely work on the
documentation for
# the compiler module.

bad_names = [name[0] for name in node.names
if name[0] not in allowed_imports ]
if any (bad_names):
if len (bad_names) > 1:
raise ImportError, "The modules %s could not be
imported." % \
", ".join (bad_names)
else:
raise ImportError, "The module %s could not be
imported." % \
bad_names[0]

def visitFrom(self, node):
modname = node.modname
if modname not in allowed_imports:
names = [name[0] for name in node.names]
if len (names) > 1:
names = ", ".join (stuff)
raise ImportError, "Could not import %s from module %s." %
\
(names, modname)

def visitName (self, node):
if node.name.startswith ('__'):
raise AttributeError, 'Cannot access attribute %s.\n' %
node.name

def visitExec(self, node):
raise SyntaxError, "Use of the exec statement is not allowed."

# Save the builtin compile function for later.

__compile = compile

def compile (source, filename, mode, *args):
v = __Visitor()
ast = compiler.parse (source, mode=mode)
compiler.visitor.walk (ast, v)

# If we got here without an exception, then we ought to be OK.
# To be truly nice, we might want to fix up the traceback in case
we
# deliberately raised the exception, so it's easy to tell the
difference
# between intentional and unintentional exceptions.

return __compile (source, filename, mode, *args)

---CUT HERE---

As neat as this code is, in the end, I discarded the idea of running
"untrusted" Python code this way. It's just too easy to screw up and
miss something (c.f. the many SQL injection vulnerabilities we've seen
over the years). And, once an "untrusted" individual has the ability
to run Python code on your box, you're basically screwed, anyway. For
example, if I wanted to eat a lot of CPU and exhaust memory on
someone's machine, I could just have Python calculate Ackerman's
function or the factorial of some giant number.

Once you've sorted out every such possible problem by developing a
comprehensive threat model and defending against every attack you can
come up with, you'll either have

A) Missed something, or
B) Written a Python interpreter in Python.

Obviously, A is bad, and B is most likely too slow to be usable,
unless you base your efforts on pypy. :)

Nonetheless, the compiler module is a fun toy to have around. You can
use it for lots of cool stuff, and I heartily recommend playing around
with it. I just wish it had better documentation. :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top