これは私が行った解決策です。このアプローチのセキュリティの説明については、を参照してください。
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
これはコードの数学的な部分のみを受け入れるように設計されており、関数定義とreturnステートメントが提供されていることに注意してください。
必要なすべての安全な構成をホワイトリストに登録し、特に必要な安全でない構成をホワイトリストに登録するこの方法は、Pythonの多くの有用なサブセットを生成するように変更できます。ユーザースクリプトに最適です!
これを安全に実行するには、名前の衝突を減らし、ユーザーコードが無限ループなどを生成した場合にタイムアウトするように、タイムアウト付きの独自のスレッドに含める必要があることに注意してください。