stringExp = "2^4"
intVal = int(stringExp) # Expected value: 16
Esto devuelve el siguiente error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int()
with base 10: '2^4'
Sé que se eval
puede solucionar esto, pero ¿no existe un método mejor y, lo que es más importante, más seguro para evaluar una expresión matemática que se almacena en una cadena?
Respuestas:
Pyparsing se puede utilizar para analizar expresiones matemáticas. En particular, fourFn.py muestra cómo analizar expresiones aritméticas básicas. A continuación, he vuelto a envolver fourFn en una clase de analizador numérico para facilitar su reutilización.
Puedes usarlo así
fuente
eval
es malvadoNota: incluso si usa set
__builtins__
toNone
, es posible que se rompa usando la introspección:Evaluar la expresión aritmética usando
ast
Puede limitar fácilmente el rango permitido para cada operación o cualquier resultado intermedio, por ejemplo, para limitar los argumentos de entrada para
a**b
:O para limitar la magnitud de los resultados intermedios:
Ejemplo
fuente
import math
?ast.parse
no es seguro. Por ejemplo,ast.parse('()' * 1000000, '<string>', 'single')
el intérprete se bloquea.if len(expr) > 10000: raise ValueError
.len(expr)
cheque? ¿O su punto es que hay errores en la implementación de Python y, por lo tanto, es imposible escribir código seguro en general?Algunas alternativas más seguras para
eval()
e * :sympy.sympify().evalf()
* SymPy
sympify
tampoco es seguro según la siguiente advertencia de la documentación.fuente
Bien, entonces el problema con eval es que puede escapar de su caja de arena con demasiada facilidad, incluso si se deshace de
__builtins__
. Todos los métodos para escapar de la caja de arena se reducen a usargetattr
oobject.__getattribute__
(a través del.
operador) para obtener una referencia a algún objeto peligroso a través de algún objeto permitido (''.__class__.__bases__[0].__subclasses__
o similar).getattr
se elimina estableciendo__builtins__
enNone
.object.__getattribute__
es la difícil, ya que no se puede quitar simplemente, tanto porqueobject
es inmutable como porque quitarlo rompería todo. Sin embargo,__getattribute__
solo se puede acceder a través del.
operador, por lo que purgar eso desde su entrada es suficiente para garantizar que eval no pueda escapar de su caja de arena.En el procesamiento de fórmulas, el único uso válido de un decimal es cuando está precedido o seguido de
[0-9]
, por lo que simplemente eliminamos todas las demás instancias de.
.Tenga en cuenta que, si bien Python normalmente lo trata
1 + 1.
como1 + 1.0
, esto eliminará el final.
y lo dejará con1 + 1
. Podría agregar)
,y
EOF
a la lista de cosas permitidas a continuación.
, pero ¿por qué molestarse?fuente
.
sea correcto o no en este momento, esto deja la posibilidad de vulnerabilidades de seguridad si las versiones futuras de Python introducen una nueva sintaxis que permita acceder a objetos o funciones inseguros de alguna otra manera. Esta solución ya no es seguro en Python 3.6 debido a f-secuencias, que permiten el ataque siguiente:f"{eval('()' + chr(46) + '__class__')}"
. Una solución basada en listas blancas en lugar de listas negras será más segura, pero realmente es mejor resolver este problema sineval
nada.Puede usar el módulo ast y escribir un NodeVisitor que verifique que el tipo de cada nodo es parte de una lista blanca.
Debido a que funciona a través de una lista blanca en lugar de una lista negra, es seguro. Las únicas funciones y variables a las que puede acceder son aquellas a las que le da acceso explícitamente. Completé un diccionario con funciones relacionadas con las matemáticas para que pueda proporcionar acceso fácilmente a ellas si lo desea, pero debe usarlo explícitamente.
Si la cadena intenta llamar a funciones que no se han proporcionado, o invoca algún método, se generará una excepción y no se ejecutará.
Debido a que utiliza el analizador y evaluador integrado de Python, también hereda las reglas de precedencia y promoción de Python.
El código anterior solo se ha probado en Python 3.
Si lo desea, puede agregar un decorador de tiempo de espera en esta función.
fuente
La razón
eval
yexec
son tan peligrosos es que lacompile
función predeterminada generará un código de bytes para cualquier expresión válida de Python, y la predeterminadaeval
oexec
ejecutará cualquier código de bytes de Python válido. Todas las respuestas hasta la fecha se han centrado en restringir el código de bytes que se puede generar (desinfectando la entrada) o en la construcción de su propio lenguaje específico de dominio utilizando el AST.En su lugar, puede crear fácilmente una
eval
función simple que es incapaz de hacer nada nefasto y puede tener fácilmente verificaciones de tiempo de ejecución en la memoria o el tiempo utilizado. Por supuesto, si se trata de matemáticas simples, entonces hay un atajo.La forma en que esto funciona es simple, cualquier expresión matemática constante se evalúa de manera segura durante la compilación y se almacena como una constante. El objeto de código devuelto por la compilación consta de
d
, cuál es el código de bytes paraLOAD_CONST
, seguido del número de la constante a cargar (generalmente el último en la lista), seguido deS
, que es el código de bytes paraRETURN_VALUE
. Si este atajo no funciona, significa que la entrada del usuario no es una expresión constante (contiene una variable o llamada de función o similar).Esto también abre la puerta a algunos formatos de entrada más sofisticados. Por ejemplo:
Esto requiere evaluar realmente el código de bytes, que aún es bastante simple. El código de bytes de Python es un lenguaje orientado a pilas, por lo que todo es una cuestión simple
TOS=stack.pop(); op(TOS); stack.put(TOS)
o similar. La clave es implementar solo los códigos de operación que son seguros (carga / almacenamiento de valores, operaciones matemáticas, valores de retorno) y no inseguros (búsqueda de atributos). Si desea que el usuario pueda llamar a funciones (laCALL_FUNCTION
única razón para no usar el atajo anterior), simplemente haga su implementación de solo permitir funciones en una lista 'segura'.Obviamente, la versión real de esto sería un poco más larga (hay 119 códigos de operación, 24 de los cuales están relacionados con las matemáticas). Agregar
STORE_FAST
y un par más permitiría una entrada similar'x=5;return x+x
o similar, de manera trivialmente fácil. Incluso se puede usar para ejecutar funciones creadas por el usuario, siempre que las funciones creadas por el usuario se ejecuten a través de VMeval (¡no las haga invocables! O podrían usarse como devolución de llamada en algún lugar). El manejo de bucles requiere soporte para losgoto
códigos de bytes, lo que significa cambiar de unfor
iteradorwhile
y mantener un puntero a la instrucción actual, pero no es demasiado difícil. Para resistir a DOS, el bucle principal debe verificar cuánto tiempo ha pasado desde el inicio del cálculo, y ciertos operadores deben negar la entrada por encima de algún límite razonable (BINARY_POWER
siendo el más obvio).Si bien este enfoque es algo más largo que un simple analizador gramatical para expresiones simples (vea más arriba acerca de simplemente tomar la constante compilada), se extiende fácilmente a entradas más complicadas y no requiere lidiar con la gramática (
compile
tome cualquier cosa arbitrariamente complicada y reduzca a una secuencia de instrucciones sencillas).fuente
Creo que lo usaría
eval()
, pero primero verificaría que la cadena sea una expresión matemática válida, en lugar de algo malicioso. Puede usar una expresión regular para la validación.eval()
también toma argumentos adicionales que puede usar para restringir el espacio de nombres en el que opera para mayor seguridad.fuente
+
,-
,*
,/
,**
,(
,)
o algo más complicadoeval()
si no controla la entrada, incluso si restringe el espacio de nombres, por ejemplo,eval("9**9**9**9**9**9**9**9", {'__builtins__': None})
consume CPU, memoria.Esta es una respuesta masivamente tardía, pero creo que es útil para futuras referencias. En lugar de escribir su propio analizador matemático (aunque el ejemplo de pyparsing anterior es excelente), podría usar SymPy. No tengo mucha experiencia con él, pero contiene un motor matemático mucho más poderoso de lo que cualquiera pueda escribir para una aplicación específica y la evaluación de la expresión básica es muy fácil:
¡Muy guay! A
from sympy import *
trae mucho más soporte de funciones, como funciones trigonométricas, funciones especiales, etc., pero lo he evitado aquí para mostrar qué viene de dónde.fuente
evalf
toma muchos Ndarrays.sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")
estas llamadassubprocess.Popen()
que pasé enls
lugar derm -rf /
. El índice probablemente será diferente en otras computadoras. Esta es una variante del exploit de Ned Batchelder[Sé que esta es una pregunta antigua, pero vale la pena señalar nuevas soluciones útiles a medida que surgen]
Desde python3.6, esta capacidad ahora está integrada en el lenguaje , acuñado "f-strings" .
Ver: PEP 498 - Interpolación de cadenas literal
Por ejemplo (tenga en cuenta el
f
prefijo):fuente
str(eval(...))
, por lo que ciertamente no es más seguro queeval
.Usar
eval
en un espacio de nombres limpio:El espacio de nombres limpio debería evitar la inyección. Por ejemplo:
De lo contrario, obtendría:
Es posible que desee dar acceso al módulo de matemáticas:
fuente
eval("""[i for i in (1).__class__.__bases__[0].__subclasses__() if i.__name__.endswith('BuiltinImporter')][0]().load_module('sys').modules['sys'].modules['os'].system('/bin/sh')""", {'__builtins__': None})
ejecuta el shell de Bourne ...This is not safe
Bueno, creo que es tan seguro como usar bash en general. Por cierto:eval('math.sqrt(2.0)')
<- "matemáticas". se requiere como se indica arriba.Aquí está mi solución al problema sin usar eval. Funciona con Python2 y Python3. No funciona con números negativos.
test.py
solution.py
fuente