¿Usa eval () de python frente a ast.literal_eval ()?

176

Tengo una situación con algún código que eval()surgió como una posible solución. Ahora nunca eval()antes había tenido que usarlo , pero he encontrado mucha información sobre el peligro potencial que puede causar. Dicho esto, soy muy cauteloso acerca de usarlo.

Mi situación es que un usuario me ha dado información:

datamap = raw_input('Provide some data here: ')

Donde datamapnecesita ser un diccionario. Busqué alrededor y descubrí que eso eval()podría funcionar. Pensé que podría verificar el tipo de entrada antes de intentar usar los datos y eso sería una precaución de seguridad viable.

datamap = eval(raw_input('Provide some data here: ')
if not isinstance(datamap, dict):
    return

Leí los documentos y todavía no estoy claro si esto sería seguro o no. ¿Eval evalúa los datos tan pronto como se ingresan o después de datamapinvocar la variable?

¿Es el astmódulo .literal_eval()la única opción segura?

tijko
fuente

Respuestas:

190

datamap = eval(raw_input('Provide some data here: '))significa que realmente evalúa el código antes de considerarlo inseguro o no. Evalúa el código tan pronto como se llama a la función. Ver también los peligros deeval .

ast.literal_eval genera una excepción si la entrada no es un tipo de datos Python válido, por lo que el código no se ejecutará si no lo es.

Úselo ast.literal_evalcuando lo necesite eval. Por lo general, no debe evaluar las declaraciones literales de Python.

Volatilidad
fuente
20
Este no es un consejo 100% correcto ya que cualquier operador bit a bit (u operador sobrecargado) fallará. P.ej. ast.literal_eval("1 & 1")arrojará un error pero eval("1 & 1")no lo hará.
Daniel van Flymen
1
Sólo curioso. ¿No deberíamos usar analizadores de expresiones o algo así si esperamos algo como "1 y 1"?
thelinuxer
@thelinuxer aún deberías, sí; simplemente no podría usarlo ast.literal_evalpara algo así (por ejemplo, podría implementar un analizador manual).
Volatilidad
104

ast.literal_eval() solo considera válido un pequeño subconjunto de la sintaxis de Python:

La cadena o nodo proporcionado solo puede consistir en las siguientes estructuras literales de Python: cadenas, números, tuplas, listas, dictos, booleanos y Ninguno.

Pasando __import__('os').system('rm -rf /a-path-you-really-care-about')al ast.literal_eval()generará un error, pero eval()estará feliz de limpiar la unidad.

Como parece que solo está permitiendo que el usuario ingrese un diccionario simple, úselo ast.literal_eval(). Hace con seguridad lo que quieres y nada más.

Licuadora
fuente
también admite cadenas de bytes (bytes de clase). P.ej. b'Hola Mundo '
XChikuX
52

eval: Esto es muy poderoso, pero también es muy peligroso si acepta cadenas para evaluar desde una entrada no confiable. Supongamos que la cadena que se evalúa es "os.system ('rm -rf /')"? Realmente comenzará a eliminar todos los archivos en su computadora.

ast.literal_eval: evalúe de forma segura un nodo de expresión o una cadena que contenga un literal de Python o una pantalla de contenedor. La cadena o nodo proporcionado solo puede consistir en las siguientes estructuras literales de Python: cadenas, bytes, números, tuplas, listas, dictos, conjuntos, booleanos, Ninguno, bytes y conjuntos.

Sintaxis:

eval(expression, globals=None, locals=None)
import ast
ast.literal_eval(node_or_string)

Ejemplo:

# python 2.x - doesn't accept operators in string format
import ast
ast.literal_eval('[1, 2, 3]')  # output: [1, 2, 3]
ast.literal_eval('1+1') # output: ValueError: malformed string


# python 3.0 -3.6
import ast
ast.literal_eval("1+1") # output : 2
ast.literal_eval("{'a': 2, 'b': 3, 3:'xyz'}") # output : {'a': 2, 'b': 3, 3:'xyz'}
# type dictionary
ast.literal_eval("",{}) # output : Syntax Error required only one parameter
ast.literal_eval("__import__('os').system('rm -rf /')") # output : error

eval("__import__('os').system('rm -rf /')") 
# output : start deleting all the files on your computer.
# restricting using global and local variables
eval("__import__('os').system('rm -rf /')",{'__builtins__':{}},{})
# output : Error due to blocked imports by passing  '__builtins__':{} in global

# But still eval is not safe. we can access and break the code as given below
s = """
(lambda fc=(
lambda n: [
    c for c in 
        ().__class__.__bases__[0].__subclasses__() 
        if c.__name__ == n
    ][0]
):
fc("function")(
    fc("code")(
        0,0,0,0,"KABOOM",(),(),(),"","",0,""
    ),{}
)()
)()
"""
eval(s, {'__builtins__':{}})

En el código anterior, ().__class__.__bases__[0]nada más que el objeto en sí. Ahora instanciamos todas las subclases , aquí nuestro enter code hereobjetivo principal es encontrar una clase llamada n a partir de ella.

Necesitamos codeobjetar y functionobjetar desde subclases instanciadas. Esta es una forma alternativa de CPythonacceder a subclases de objetos y adjuntar el sistema.

Desde python 3.7 ast.literal_eval () ahora es más estricto. La suma y resta de números arbitrarios ya no están permitidos. enlace

Kiran Kumar Kotari
fuente
1
Estoy usando Python 2.7 y acabo de comprobar que funciona bien en Python 3.x. Lo malo es que lo
intenté
3
ast.literal_eval("1+1")no funciona en python 3.7 y, como se dijo antes, literal_eval debería limitarse a literales de esas pocas estructuras de datos. No debería poder analizar una operación binaria.
Sesshu
¿Podría explicar su KABOOMcódigo, por favor? Lo encontré aquí:KABOOM
winklerrr
3
@winklerrr KABOOMse explica muy bien aquí: nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
Elijas
41

Python está ansioso por su evaluación, por eval(raw_input(...))lo que evaluará la entrada del usuario tan pronto como llegue eval, independientemente de lo que haga con los datos después. Por lo tanto, esto no es seguro , especialmente cuando ingresa el evalusuario.

Uso ast.literal_eval.


Como ejemplo, ingresar esto en el indicador será muy, muy malo para usted:

__import__('os').system('rm -rf /a-path-you-really-care-about')
nneonneo
fuente
3

Si todo lo que necesita es un diccionario proporcionado por el usuario, es posible una mejor solución json.loads. La principal limitación es que json dicts requiere claves de cadena. Además, solo puede proporcionar datos literales, pero ese es también el caso literal_eval.

Chinasaur
fuente
1

Estaba atrapado con ast.literal_eval(). Lo estaba intentando en el depurador IntelliJ IDEA, y seguía volviendo Noneen la salida del depurador.

Pero más tarde cuando asigné su salida a una variable y la imprimí en código. Funcionó bien. Ejemplo de código compartido:

import ast
sample_string = '[{"id":"XYZ_GTTC_TYR", "name":"Suction"}]'
output_value = ast.literal_eval(sample_string)
print(output_value)

Su versión de Python 3.6.

M Haziq
fuente