Usando python's eval () vs. ast.literal_eval ()?


176

Ho una situazione con del codice in cui eval()è emersa come possibile soluzione. Ora non ho mai dovuto usareeval() prima, ma ho trovato molte informazioni sul potenziale pericolo che può causare. Detto questo, sono molto diffidente nell'usarlo.

La mia situazione è che ho ricevuto input da un utente:

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

Dove datamapdeve essere un dizionario. Ho cercato in giro e ho scoperto che eval()poteva risolverlo. Ho pensato che avrei potuto controllare il tipo di input prima di provare a utilizzare i dati e che sarebbe stata una precauzione di sicurezza praticabile.

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

Ho letto i documenti e non sono ancora chiaro se questo sarebbe sicuro o meno. Valuta i dati non appena inseriti o dopo ildatamap variabile è stata chiamata?

astL' .literal_eval()unica opzione sicura è il modulo ?

Risposte:


190

datamap = eval(raw_input('Provide some data here: '))significa che si valuta effettivamente il codice prima di ritenerlo pericoloso o meno. Valuta il codice non appena viene chiamata la funzione. Vedi anche i pericoli dieval .

ast.literal_eval solleva un'eccezione se l'input non è un tipo di dati Python valido, quindi il codice non verrà eseguito in caso contrario.

Usa ast.literal_evalquando ne hai bisogno eval. Di solito non dovresti valutare le affermazioni letterali di Python.


20
Questo non è un consiglio corretto al 100% poiché eventuali operatori bit a bit (o operatori sovraccaricati) falliranno. Per esempio. ast.literal_eval("1 & 1")genererà un errore ma eval("1 & 1")non lo farà.
Daniel van Flymen,

1
Solo curioso. Non dovremmo usare i parser di espressione o qualcosa del genere se ci aspettiamo qualcosa come "1 & 1"?
Linuxer il

@thelinuxer dovresti ancora, sì; semplicemente non saresti in grado di utilizzarlo ast.literal_evalper qualcosa del genere (ad esempio potresti implementare manualmente un parser).
Volatilità il

104

ast.literal_eval() considera valido solo un piccolo sottoinsieme della sintassi di Python:

La stringa o il nodo forniti possono essere costituiti solo dalle seguenti strutture letterali Python: stringhe, numeri, tuple, elenchi, dadi, valori booleani e Nessuno.

Passare __import__('os').system('rm -rf /a-path-you-really-care-about')a ast.literal_eval()genererà un errore, maeval() cancellerà felicemente l'unità.

Dal momento che sembra che stai solo permettendo all'utente di inserire un dizionario semplice, usa ast.literal_eval(). Fa tranquillamente quello che vuoi e niente di più.


supporta anche stringhe di byte (byte di classe). Per esempio. b'Hello World '
XChikuX,

52

eval: questo è molto potente, ma è anche molto pericoloso se si accettano stringhe da valutare da input non attendibile. Supponiamo che la stringa valutata sia "os.system ('rm -rf /')"? Inizierà davvero a eliminare tutti i file sul tuo computer.

ast.literal_eval: valuta in modo sicuro un nodo di espressione o una stringa contenente un valore letterale o contenitore Python. La stringa o il nodo forniti possono essere costituiti solo dalle seguenti strutture letterali Python: stringhe, byte, numeri, tuple, elenchi, dadi, set, valori booleani, Nessuno, byte e set.

Sintassi:

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

Esempio:

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

Nel codice precedente ().__class__.__bases__[0]nient'altro che oggetto stesso. Ora abbiamo istanziato tutte le sottoclassi , qui il nostro enter code hereobiettivo principale è trovare una classe chiamata n da essa.

Dobbiamo codeobiettare e functionobiettare da sottoclassi istanziate. Questo è un modo alternativo CPythonper accedere a sottoclassi di oggetti e collegare il sistema.

Da python 3.7 ast.literal_eval () è ora più rigoroso. L'aggiunta e la sottrazione di numeri arbitrari non sono più consentite. collegamento


1
sto usando Python 2.7 e ho appena verificato il suo corretto funzionamento su Python 3.x. Mio male, ho continuato a provarlo su Python 2.7
Mourya,

3
ast.literal_eval("1+1")non funziona in Python 3.7 e, come detto prima, literal_eval dovrebbe essere limitato ai letterali di quelle poche strutture di dati. Non dovrebbe essere in grado di analizzare un'operazione binaria.
Sesshu,

Potresti spiegare il tuo KABOOMcodice, per favore? L'ho trovato qui:KABOOM
winklerrr il

3
@winklerrr KABOOMè ben spiegato qui: nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
Elijas

41

Python è impaziente nella sua valutazione, quindi eval(raw_input(...))valuterà l'input dell'utente non appena lo colpisce eval, indipendentemente da ciò che fai successivamente con i dati. Pertanto, questo non è sicuro , soprattutto quando sieval utente immette.

Usa ast.literal_eval.


Ad esempio, immetterlo al prompt sarà molto, molto male per te:

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

3

Se tutto ciò che serve è un dizionario fornito dall'utente, la soluzione migliore possibile è json.loads. Il limite principale è che json dicts richiede chiavi di stringa. Inoltre puoi fornire solo dati letterali, ma è anche il caso literal_eval.


1

Ero bloccato con ast.literal_eval(). Lo stavo provando nel debugger IntelliJ IDEA, e continuava a tornare Nonesull'output del debugger.

Ma più tardi, quando ho assegnato il suo output a una variabile e l'ho stampato in codice. Ha funzionato bene. Esempio di codice di condivisione:

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

La sua versione 3.6 di Python.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.