Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Restricting Python's syntax to execute user code safely. Is this a safe approach?

Original question:

Executing mathematical user code on a python web server, what is the simplest secure way?

  • I want to be able to run user submitted code on a python webserver. The code will be simple and mathematical in nature.

As such a small subset of Python is required, my current approach is to whitelist allowable syntax by traversing Python's abstract syntax tree. Functions and names get special treatment; only explicitly whitelisted functions are allowed, and only unused names.

import ast

allowed_functions = set([
    #math library
    'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh',
    'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf',
    'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod',
    'frexp', 'fsum', 'gamma', 'hypot', 'isinf', 'isnan', 'ldexp',
    'lgamma', 'log', 'log10', 'log1p', 'modf', 'pi', 'pow', 'radians',
    'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc',
    #builtins
    'abs', 'max', 'min', 'range', 'xrange'
    ])

allowed_node_types = set([
    #Meta
    'Module', 'Assign', 'Expr',
    #Control
    'For', 'If', 'Else',
    #Data
    'Store', 'Load', 'AugAssign', 'Subscript',
    #Datatypes
    'Num', 'Tuple', 'List',
    #Operations
    'BinOp', 'Add', 'Sub', 'Mult', 'Div', 'Mod', 'Compare'
    ])

safe_names = set([
    'True', 'False', 'None'
    ])


class SyntaxChecker(ast.NodeVisitor):

    def check(self, syntax):
        tree = ast.parse(syntax)
        self.visit(tree)

    def visit_Call(self, node):
        if node.func.id not in allowed_functions:
            raise SyntaxError("%s is not an allowed function!"%node.func.id)
        else:
            ast.NodeVisitor.generic_visit(self, node)

    def visit_Name(self, node):
        try:
            eval(node.id)
        except NameError:
            ast.NodeVisitor.generic_visit(self, node)
        else:
            if node.id not in safe_names and node.id not in allowed_functions:
                raise SyntaxError("%s is a reserved name!"%node.id)
            else:
                ast.NodeVisitor.generic_visit(self, node)

    def generic_visit(self, node):
        if type(node).__name__ not in allowed_node_types:
            raise SyntaxError("%s is not allowed!"%type(node).__name__)
        else:
            ast.NodeVisitor.generic_visit(self, node)

if __name__ == '__main__':
    x = SyntaxChecker()
    while True:
        try:
            x.check(raw_input())
        except Exception as e:
            print e

This seems to accept the required syntax, but I am reasonably new to programming and could be missing any number of gaping security holes.

So my questions are: Is this secure, is there a better approach, and are there any other precautions I should be taking?

like image 908
SudoNhim Avatar asked May 18 '12 23:05

SudoNhim


People also ask

Is Python a security risk?

But like all programming languages, Python is not immune to security threats. Secure coding best practices must be adopted to avoid risks from attackers. In this post, we'll explore Python security best practices that should employed when building secure application.

What is Python security?

The general rule is any attack worth reporting via the security address must allow an attacker to affect the confidentiality, integrity and availability of the Python application or its system for which the attacker does not already have the capability.


2 Answers

Have you looked at pypy's sandboxing features? It is reputedly much safer than any CPython sandboxing efforts. You can even limit the heap size and cpu execution time to prevent denial of service.

like image 89
Andrew Gorcester Avatar answered Oct 16 '22 10:10

Andrew Gorcester


Two points I noticed that you could still improve:

You should always escape any output that can be generated from some form of user input. In your example, the unallowed identifiers get mirrored unmodified back to the output. This could potentially be exploited, one example being Cross Site Scripting. Therefore I would additionally escape any such error message to prevent this.

Another thing you need to be aware of is Denial-of-Service attacks. Imagine someone whips up an Ackermann function and a script to submit it a couple of thousand times to your server... To prevent this, you should timebox the execution time of any code being submitted. This is essential, because this type of "attack" often happens unintentionally - someone managed to produce an infinite loop.

Edit:

Finally, I would also recommend to update your Python version to prevent a "hashDoS" attack.

like image 22
emboss Avatar answered Oct 16 '22 08:10

emboss