Restricción de la syntax de Python para ejecutar el código de usuario de forma segura. ¿Es este un enfoque seguro?

Pregunta original:

Ejecutando un código de usuario matemático en un servidor web de Python, ¿cuál es la forma segura más simple?

  • Quiero poder ejecutar el código enviado por el usuario en un servidor web de Python. El código será simple y matemático por naturaleza.

Como se requiere un pequeño subconjunto de Python, mi enfoque actual es agregar a la lista blanca la syntax permisible atravesando el árbol de syntax abstracta de Python. Funciones y nombres reciben un trato especial; solo se permiten funciones explícitamente incluidas en la lista blanca, y solo nombres no utilizados.

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 

Esto parece aceptar la syntax requerida, pero soy razonablemente nuevo en la progtwigción y podría faltar una gran cantidad de agujeros de seguridad.

Entonces, mis preguntas son: ¿es seguro, hay un mejor enfoque y hay otras precauciones que debería tomar?

Dos puntos me di cuenta de que todavía podrías mejorar:

Siempre debe escapar de cualquier salida que pueda generarse a partir de algún tipo de entrada del usuario. En su ejemplo, los identificadores no permitidos se duplican sin modificar de nuevo en la salida. Esto podría potencialmente ser explotado, un ejemplo es el Cross Site Scripting . Por lo tanto, me gustaría escapar de cualquier mensaje de error para evitar esto.

Otra cosa que debes tener en cuenta es los ataques de denegación de servicio. Imagine que alguien prepara una función de Ackermann y una secuencia de comandos para enviarla un par de miles de veces a su servidor … Para evitar esto, debe registrar el tiempo de ejecución de cualquier código enviado. Esto es esencial, porque este tipo de “ataque” a menudo ocurre involuntariamente: alguien logró producir un bucle infinito.

Editar:

Finalmente, también recomendaría actualizar su versión de Python para evitar un ataque “hashDoS” .

El código fuente del Openerp contiene un safe_eval.py que hace algo similar. Pero en lugar de verificar el ast de la fuente, restringe el código de byte que se permite ejecutar. Creo que también puedes echarle un vistazo 🙂

¿Has visto las características de sandboxing de Pypy ? Se dice que es mucho más seguro que cualquier esfuerzo de sandboxing de CPython. Incluso puede limitar el tamaño del montón y el tiempo de ejecución de la CPU para evitar la denegación de servicio.