5

Here is the scenario, my website has some unsafe code, which is generated by website users, to run on my server.

I want to disable some reserved words for python to protect my running environment, such as eval, exec, print and so on.

Is there a simple way (without changing the python interpreter, my python version is 2.7.10) to implement the feature I described before?

Many thanks.

Kris
  • 1,358
  • 13
  • 24
diaosihuai
  • 151
  • 6
  • Is your website designed to be a sandbox in the first place, or is this something that's happening because people are trying to attack your site? – Burhan Khalid Oct 21 '15 at 06:19
  • you can validate user input before processing anything – Hackaholic Oct 21 '15 at 06:20
  • It is designed to be a sandbox which allow users execute python codes to access user-defined data which is located on my server. – diaosihuai Oct 21 '15 at 06:21
  • `eval` is not a keyword, but a function. `eval = 8; print eval`; Best bet for validating code is to parse it into [AST](https://docs.python.org/2/library/ast.html), then scan the tree for unsafe things. However... see [this](http://programmers.stackexchange.com/questions/191623/best-practices-for-execution-of-untrusted-code). – Amadan Oct 21 '15 at 06:23
  • Thanks for reminding `eval` and the suggestion. – diaosihuai Oct 21 '15 at 06:31
  • Speaking of `eval`, see [Eval really is dangerous](http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html) by SO vetreran Ned Batchelder. A couple of the comments to that article discuss using the `ast` module to sanitize user-supplied code. – PM 2Ring Oct 21 '15 at 06:33
  • Your *other* problem is denial-of-service by means of a near-infinite loop in "safe" code. `sum(bool(x%2) for x in range(10) )` is "safe" but replace 10 by 1000000000000 ... – nigel222 Oct 21 '15 at 08:30
  • @nigel222 I am thinking about using a timeout mechanism to avoid that scenario. – diaosihuai Oct 22 '15 at 10:45

2 Answers2

4

Disabling names on python level won't help as there are numerous ways around it. See this and this post for more info. This is what you need to do:

For CPython, use RestrictedPython to define a restricted subset of Python.

For PyPy, use sandboxing. It allows you to run arbitrary python code in a special environment that serializes all input/output so you can check it and decide which commands are allowed before actually running them.

Since version 3.8 Python supports audit hooks so you can completely prevent certain actions:

import sys

def audit(event, args):
    if event == 'compile':
        sys.exit('nice try!')

sys.addaudithook(audit)

eval('5')

Additionally, to protect your host OS, use

  • either virtualization (safer) such as KVM or VirtualBox

  • or containerization (much lighter) such as lxd or docker

In the case of containerization with docker you may need to add AppArmor or SELinux policies for extra safety. lxd already comes with AppArmor policies by default.

Make sure you run the code as a user with as little privileges as possible.

Rebuild the virtual machine/container for each user.

Whichever solution you use, don't forget to limit resource usage (RAM, CPU, storage, network). Use cgroups if your chosen virtualization/containerization solution does not support these kinds of limits.

Last but not least, use timeouts to prevent your users' code from running forever.

Kris
  • 1,358
  • 13
  • 24
0

One way is to shadow the methods:

def not_available(*args, **kwargs):
    return 'Not allowed'

eval = not_available
exec = not_available
print = not_available

However, someone smart can always do this:

import builtins
builtins.print('this works!')

So the real solution is to parse the code and not allow the input if it has such statements (rather than trying to disable them).

Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284
  • Good for you! I also try to disable some specific modules such as os, threading and builtins... – diaosihuai Oct 21 '15 at 06:27
  • 1
    Unfortunately, this won't work for `exec` in Python 2.7 since `exec` is a statement, not a function, so any attempt to assign to `exec` raises `SyntaxError: invalid syntax`. And to make it work for `print` you need `from __future__ import print_function` to disable the `print` statement. – PM 2Ring Oct 21 '15 at 06:30
  • @diaosihuai: You can do a simple string or regex test to see if the user code contains an exec statement. OTOH, it's probably better to use [ast](https://docs.python.org/2/library/ast.html) to process the user code. – PM 2Ring Oct 21 '15 at 06:39
  • @PM 2Ring use ast to check whether user code contains these sensitive keywords and stop if the result is True. So the user code will not really run until they pass the ast test? – diaosihuai Oct 21 '15 at 06:44