Usando eval () do python vs. ast.literal_eval ()?

176

Eu tenho uma situação com algum código que eval()surgiu como uma possível solução. Agora eu nunca tive que usar eval()antes, mas encontrei muitas informações sobre o perigo potencial que isso pode causar. Dito isto, sou muito cauteloso em usá-lo.

Minha situação é que eu recebo informações de um usuário:

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

Onde datamapprecisa ser um dicionário. Eu procurei e descobri que isso eval()poderia resolver isso. Eu pensei que poderia verificar o tipo de entrada antes de tentar usar os dados e isso seria uma precaução de segurança viável.

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

Eu li os documentos e ainda não estou claro se isso seria seguro ou não. O eval avalia os dados assim que são inseridos ou depois que a datamapvariável é chamada?

O astmódulo é .literal_eval()a única opção segura?

tijko
fonte

Respostas:

189

datamap = eval(raw_input('Provide some data here: '))significa que você realmente avalia o código antes de considerá-lo inseguro ou não. Ele avalia o código assim que a função é chamada. Veja também os perigos deeval .

ast.literal_eval gera uma exceção se a entrada não for um tipo de dados Python válido, portanto o código não será executado se não for.

Use ast.literal_evalsempre que precisar eval. Normalmente, você não deve avaliar instruções literais em Python.

Volatilidade
fonte
20
Este não é um conselho 100% correto, pois qualquer operador bit a bit (ou sobrecarregado) falhará. Por exemplo. ast.literal_eval("1 & 1")irá lançar um erro, mas eval("1 & 1")não irá.
Daniel van Flymen
1
Apenas curioso. Não devemos usar analisadores de expressão ou algo assim, se esperamos algo como "1 e 1"?
Theluxer
@ thelinuxer você ainda deveria, sim; você simplesmente não seria capaz de usar ast.literal_evalalgo assim (por exemplo, você poderia implementar um analisador manualmente).
Volatilidade
104

ast.literal_eval() considera apenas um pequeno subconjunto da sintaxe do Python válido:

A cadeia ou nó fornecido pode consistir apenas nas seguintes estruturas literais do Python: cadeias, números, tuplas, listas, dictos, booleanos e Nenhum.

Passar __import__('os').system('rm -rf /a-path-you-really-care-about')para ast.literal_eval()ele gera um erro, mas eval()limpa sua unidade com prazer.

Como parece que você está apenas deixando o usuário inserir um dicionário simples, use ast.literal_eval(). Ele faz com segurança o que você quer e nada mais.

Liquidificador
fonte
suporta byte-strings (bytes de classe) também. Por exemplo. b'Hello World '
XChikuX 01/02/19
52

eval: Isso é muito poderoso, mas também é muito perigoso se você aceitar seqüências de caracteres para avaliar a partir de informações não confiáveis. Suponha que a string que está sendo avaliada seja "os.system ('rm -rf /')"? Ele realmente começará a excluir todos os arquivos do seu computador.

ast.literal_eval: avalie com segurança um nó de expressão ou uma string que contém uma exibição literal de container ou contêiner do Python. A cadeia ou nó fornecido pode consistir apenas nas seguintes estruturas literais do Python: cadeias, bytes, números, tuplas, listas, dictos, conjuntos, booleanos, Nenhum, bytes e conjuntos.

Sintaxe:

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

Exemplo:

# 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__':{}})

No código acima, ().__class__.__bases__[0]nada além de objeto em si. Agora que instanciamos todas as subclasses , aqui nosso enter code hereobjetivo principal é encontrar uma classe chamada n a partir dela.

Precisamos codeobjetar e functionobjetar a partir de subclasses instanciadas. Essa é uma maneira alternativa de CPythonacessar subclasses de objeto e anexar o sistema.

A partir do python 3.7, ast.literal_eval () agora é mais rigoroso. A adição e subtração de números arbitrários não são mais permitidas. ligação

Kiran Kumar Kotari
fonte
1
Estou usando python 2.7 e acabei de verificar o seu bom funcionamento no python 3.x. My bad i continuou tentando-lo em python 2.7
Mourya
3
ast.literal_eval("1+1")não funciona no python 3.7 e, como dito anteriormente, o literal_eval deve ser limitado aos literais dessas poucas estruturas de dados. Não deve ser capaz de analisar uma operação binária.
Sesshu 3/10
Você poderia explicar seu KABOOMcódigo, por favor? Encontre-o aqui:KABOOM
winklerrr 4/01/19
2
@winklerrr KABOOMé bem explicado aqui: nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
Elijas
41

O Python está ansioso em sua avaliação, por eval(raw_input(...))isso avaliará a entrada do usuário assim que ela chegar eval, independentemente do que você faz com os dados posteriormente. Portanto, isso não é seguro , especialmente quando você evalinsere o usuário.

Use ast.literal_eval.


Como exemplo, digitar isso no prompt será muito, muito ruim para você:

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

Se tudo o que você precisa é de um dicionário fornecido pelo usuário, é possível a melhor solução json.loads. A principal limitação é que o json dicts requer chaves de string. Além disso, você só pode fornecer dados literais, mas esse também é o caso literal_eval.

Chinasaur
fonte
1

Eu estava preso ast.literal_eval(). Eu estava tentando no depurador IntelliJ IDEA e ele continuava retornandoNone na saída do depurador.

Mas mais tarde, quando atribuí sua saída a uma variável e a imprimi em código. Funcionou bem. Exemplo de código de compartilhamento:

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

Sua versão python 3.6.

M Haziq
fonte