funcion example comando python eval python-internals

example - Evaluación de Python: ¿sigue siendo peligroso si deshabilito los accesos integrados y los atributos?



python exec (6)

Aquí hay un ejemplo de safe_eval que asegurará que la expresión evaluada no contenga tokens no seguros. No intenta adoptar el enfoque literal de la interpretación del AST, sino que hace una lista blanca de los tipos de token y usa la evaluación real si la expresión pasa la prueba.

# license: MIT (C) tardyp import ast def safe_eval(expr, variables): """ Safely evaluate a a string containing a Python expression. The string or node provided may only consist of the following Python literal structures: strings, numbers, tuples, lists, dicts, booleans, and None. safe operators are allowed (and, or, ==, !=, not, +, -, ^, %, in, is) """ _safe_names = {''None'': None, ''True'': True, ''False'': False} _safe_nodes = [ ''Add'', ''And'', ''BinOp'', ''BitAnd'', ''BitOr'', ''BitXor'', ''BoolOp'', ''Compare'', ''Dict'', ''Eq'', ''Expr'', ''Expression'', ''For'', ''Gt'', ''GtE'', ''Is'', ''In'', ''IsNot'', ''LShift'', ''List'', ''Load'', ''Lt'', ''LtE'', ''Mod'', ''Name'', ''Not'', ''NotEq'', ''NotIn'', ''Num'', ''Or'', ''RShift'', ''Set'', ''Slice'', ''Str'', ''Sub'', ''Tuple'', ''UAdd'', ''USub'', ''UnaryOp'', ''boolop'', ''cmpop'', ''expr'', ''expr_context'', ''operator'', ''slice'', ''unaryop''] node = ast.parse(expr, mode=''eval'') for subnode in ast.walk(node): subnode_name = type(subnode).__name__ if isinstance(subnode, ast.Name): if subnode.id not in _safe_names and subnode.id not in variables: raise ValueError("Unsafe expression {}. contains {}".format(expr, subnode.id)) if subnode_name not in _safe_nodes: raise ValueError("Unsafe expression {}. contains {}".format(expr, subnode_name)) return eval(expr, variables) class SafeEvalTests(unittest.TestCase): def test_basic(self): self.assertEqual(safe_eval("1", {}), 1) def test_local(self): self.assertEqual(safe_eval("a", {''a'': 2}), 2) def test_local_bool(self): self.assertEqual(safe_eval("a==2", {''a'': 2}), True) def test_lambda(self): self.assertRaises(ValueError, safe_eval, "lambda : None", {''a'': 2}) def test_bad_name(self): self.assertRaises(ValueError, safe_eval, "a == None2", {''a'': 2}) def test_attr(self): self.assertRaises(ValueError, safe_eval, "a.__dict__", {''a'': 2}) def test_eval(self): self.assertRaises(ValueError, safe_eval, "eval(''os.exit()'')", {}) def test_exec(self): self.assertRaises(SyntaxError, safe_eval, "exec ''import os''", {}) def test_multiply(self): self.assertRaises(ValueError, safe_eval, "''s'' * 3", {}) def test_power(self): self.assertRaises(ValueError, safe_eval, "3 ** 3", {}) def test_comprehensions(self): self.assertRaises(ValueError, safe_eval, "[i for i in [1,2]]", {''i'': 1})

Todos sabemos que eval es peligroso , incluso si ocultas funciones peligrosas, porque puedes usar las características de introspección de Python para profundizar en las cosas y volver a extraerlas. Por ejemplo, incluso si elimina __builtins__ , puede recuperarlos con

[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == ''catch_warnings''][0]()._module.__builtins__

Sin embargo, todos los ejemplos que he visto de esto usan acceso a atributos. ¿Qué sucede si deshabilito todos los elementos incorporados y deshabilito el acceso a los atributos (al crear un token de la entrada con un tokenizador de Python y rechazarla si tiene un token de acceso al atributo)?

Y antes de preguntar, no, para mi caso de uso, no necesito ninguno de estos, por lo que no es demasiado paralizante.

Lo que estoy tratando de hacer es hacer que la función sympify de sympify más segura. Actualmente tokeniza la entrada, realiza algunas transformaciones en ella y la guarda en un espacio de nombres. Pero no es seguro porque permite el acceso a los atributos (aunque en realidad no lo necesita).


Controlar los diccionarios locals y globals es extremadamente importante. De lo contrario, alguien podría aprobar eval o exec y llamarlo de forma recursiva.

safe_eval(''''''e("""[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == /'catch_warnings/'][0]()._module.__builtins__""")'''''', globals={''e'': eval})

La expresión en la eval recursiva es solo una cadena.

También debe configurar los nombres de eval y exec en el espacio de nombres global a algo que no sea real eval o exec . El espacio de nombres global es importante. Si usa un espacio de nombres local, cualquier cosa que cree un espacio de nombres separado, como las comprensiones y las lambdas, funcionará a su alrededor.

safe_eval(''''''[eval("""[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == /'catch_warnings/'][0]()._module.__builtins__""") for i in [1]][0]'''''', locals={''eval'': None}) safe_eval(''''''(lambda: eval("""[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == /'catch_warnings/'][0]()._module.__builtins__"""))()'''''', locals={''eval'': None})

Nuevamente, aquí, safe_eval solo ve una cadena y una llamada de función, no atributos de acceso.

También debe borrar la función safe_eval , si tiene un indicador para deshabilitar el análisis seguro. De lo contrario simplemente podrías hacer

safe_eval(''safe_eval("<dangerous code>", safe=False)'')


Es posible construir un valor de retorno desde eval que arrojaría una excepción fuera de eval si intentara print , log , repr cualquier cosa:

eval(''''''((lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))) (lambda f: lambda n: (1,(1,(1,(1,f(n-1))))) if n else 1)(300))'''''')

Esto crea una tupla de forma anidada (1,(1,(1,(1... ; ese valor no puede print (en Python 3), str o repr ; todos los intentos de depuración llevaría a

RuntimeError: maximum recursion depth exceeded while getting the repr of a tuple

pprint y saferepr fallan también:

... File "/usr/lib/python3.4/pprint.py", line 390, in _safe_repr orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level) File "/usr/lib/python3.4/pprint.py", line 340, in _safe_repr if issubclass(typ, dict) and r is dict.__repr__: RuntimeError: maximum recursion depth exceeded while calling a Python object

Por lo tanto, no hay una función incorporada segura para esta cadena: el siguiente asistente podría ser de utilidad:

def excsafe_repr(obj): try: return repr(obj) except: return object.__repr__(obj).replace(''>'', '' [exception raised]>'')

Y luego está el problema de que print en Python 2 en realidad no usa str / repr , por lo que no tiene ninguna seguridad debido a la falta de controles de recursión. Es decir, tome el valor de retorno del monstruo lambda de arriba, y no puede print_function , reproducirlo, pero la print normal (no print_function !) Lo imprime muy bien. Sin embargo, puede explotar esto para generar un SIGSEGV en Python 2 si sabe que se imprimirá utilizando la declaración de print :

print eval(''(lambda i: [i for i in ((i, 1) for j in range(1000000))][-1])(1)'')

se estrella Python 2 con SIGSEGV . Esto es WONTFIX en el rastreador de errores . Por lo tanto, nunca use print -the-statement si quiere estar seguro. from __future__ import print_function !

Esto no es un choque, pero

eval(''(1,'' * 100 + '')'' * 100)

cuando se ejecuta, salidas

s_push: parser Traceback (most recent call last): File "yyy.py", line 1, in <module> eval(''(1,'' * 100 + '')'' * 100) MemoryError

El MemoryError puede ser capturado, es una subclase de Exception . El analizador tiene algunos límites realmente conservadores para evitar bloqueos de los flujos de apilamiento (juego de palabras destinado). Sin embargo, el s_push: parser a stderr mediante un código C y no se puede suprimir.

Y ayer pregunté por qué Python 3.4 no se arregla para un choque desde

% python3 Python 3.4.3 (default, Mar 26 2015, 22:03:40) [GCC 4.9.2] on linux Type "help", "copyright", "credits" or "license" for more information. >>> class A: ... def f(self): ... nonlocal __x ... [4] 19173 segmentation fault (core dumped) python3

y la respuesta de Serhiy Storchaka confirmó que los desarrolladores de Python Core no consideran a SIGSEGV en un código aparentemente bien formado como un problema de seguridad:

Solo se aceptan correcciones de seguridad para 3.4.

Por lo tanto, se puede concluir que nunca se puede considerar seguro ejecutar cualquier código de terceros en Python, ya sea saneado o no.

Y Nick Coghlan luego added :

Y como algunos antecedentes adicionales de por qué las fallas de segmentación provocadas por el código de Python no se consideran actualmente un error de seguridad: como CPython no incluye un recinto de seguridad, ya dependemos completamente del sistema operativo para proporcionar aislamiento del proceso. El límite de seguridad a nivel del sistema operativo no se ve afectado por si el código se ejecuta "normalmente" o en un estado modificado luego de un fallo de segmentación activado deliberadamente.


Los usuarios pueden seguir haciéndolo ingresando una expresión que se evalúa en un número enorme, que llenaría su memoria y colapsaría el proceso de Python, por ejemplo

''10**10**100''

Definitivamente sigo sintiendo curiosidad por los ataques más tradicionales, como la recuperación de incorporaciones o la creación de una falla de seguridad, aquí es posible.

EDITAR:

Resulta que incluso el analizador de Python tiene este problema.

lambda: 10**10**100

Se cuelga, porque trata de calcular previamente la constante.


No creo que Python esté diseñado para tener seguridad contra códigos no confiables. Aquí hay una manera fácil de inducir un segfault a través del desbordamiento de pila (en la pila C) en el intérprete oficial de Python 2:

eval(''()'' * 98765)

Desde mi answer al "Código más corto que devuelve SIGSEGV", pregunta sobre el Código de Golf.


Voy a mencionar una de las nuevas características de Python 3.6 - f-strings .

Pueden evaluar expresiones,

>>> eval(''f"{().__class__.__base__}"'', {''__builtins__'': None}, {}) "<class ''object''>"

pero el atributo de acceso no será detectado por el tokenizador de Python:

0,0-0,0: ENCODING ''utf-8'' 1,0-1,1: ERRORTOKEN "''" 1,1-1,27: STRING ''f"{().__class__.__base__}"'' 2,0-2,0: ENDMARKER ''''