这是我采用的解决方案。关于这种方法的安全性讨论,请参见以下链接:
限制Python语法以安全地执行用户代码,这种方法是否安全?
由于
arifwn 的帮助,我开始探索Python的抽象语法树(AST)模块。该模块提供了一个
ast.NodeVisitor
类用于遍历整个树。本代码通过子类化
NodeVisitor
来创建一个语法检查器,该检查器仅允许基本数学所需的代码。函数调用和名称特别受到监控,因为只有特定的函数应该被允许,只有未使用的名称应该被允许。
import ast
allowed_functions = set([
'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',
'abs', 'max', 'min', 'range', 'xrange'
])
allowed_node_types = set([
'Module', 'Assign', 'Expr',
'For', 'If', 'Else',
'Store', 'Load', 'AugAssign', 'Subscript',
'Num', 'Tuple', 'List',
'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子集;非常适合用户脚本!
请注意,为了安全执行此操作,应将其放在自己的线程中并设置超时时间,以减少名称冲突,并在用户代码生成无限循环或类似情况时超时。