在 Python Web 服务器上执行数学用户代码,最简单且安全的方法是什么?

8

我知道这个问题以前已经被问过了,但是这次情况稍有不同。

我想运行一个Python图像板(使用web.py),允许用户通过提交代码来生成新的图像。 代码将采用单个函数的形式,该函数接受像素的x,y坐标并返回r,g,b值,例如:

def simpleGradient(xrel,yrel):
    r = xrel*256
    g = yrel*256
    b = 0
    return [r,g,b]

只需要非常简单的语法,不一定需要使用Python。使用有限范围的exec似乎过于不安全,而使用PyPy或VM则显得过于复杂(我对这些都比较陌生)。

除了沙箱,还有没有一种Pythonic的方式可以在一个更小的语言中执行代码?可以是Python的子集(解析和白名单?),或者是一个我可以嵌入的数学导向的语言?


2
我实际上会使用 PyPy 沙盒。 - Jonathan Leonard
我读了几个其他答案都反对它...所以我并没有真正研究过PyPy - 谢谢,我会去看看的。 - SudoNhim
好问题,也许PyPy是答案。今天刚谈到Python在这里可能有点短,比如说Lua。 - Skylar Saveland
1
如果你有时间,我认为使用Python的内部编译器来自己制作会很有趣:https://dev59.com/K3RB5IYBdhLWcg3wiHv7 - arifwn
1
哇...我一直在考虑创建自己的编程语言(目前正在为大学作业写PL0编译器),但这种方法可能更有趣! - SudoNhim
2个回答

3
这是我采用的解决方案。关于这种方法的安全性讨论,请参见以下链接:限制Python语法以安全地执行用户代码,这种方法是否安全? 由于 arifwn 的帮助,我开始探索Python的抽象语法树(AST)模块。该模块提供了一个ast.NodeVisitor类用于遍历整个树。本代码通过子类化NodeVisitor来创建一个语法检查器,该检查器仅允许基本数学所需的代码。函数调用和名称特别受到监控,因为只有特定的函数应该被允许,只有未使用的名称应该被允许。
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.passed=True
        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

请注意,此设计仅接受代码的数学部分,函数定义和返回语句已提供。
白名单方式可以列出所有必需的安全构造,并特别列出必需的不安全构造,可修改为生成许多有用的Python子集;非常适合用户脚本!
请注意,为了安全执行此操作,应将其放在自己的线程中并设置超时时间,以减少名称冲突,并在用户代码生成无限循环或类似情况时超时。

我认为这应该是一个独立的问题。 - TryPyPy
抱歉。我认识一个人,希望他能帮我检查一下;如果他觉得没问题,我会重新格式化它,使其更像一个答案。(否则我会删除它)。 - SudoNhim
我的意思是:如果你把这个回答转化为一个新的问题,你会得到更多的关注(可能还会有新的建议):) - TryPyPy
已移至https://dev59.com/umgv5IYBdhLWcg3wW_h_。 - SudoNhim

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接