Używając funkcji eval () w Pythonie w porównaniu z ast.literal_eval ()?


176

Mam sytuację z kodem, w którym eval()pojawił się jako możliwe rozwiązanie. Teraz nigdy wcześniej nie musiałem używać, eval()ale natknąłem się na wiele informacji o potencjalnym niebezpieczeństwie, jakie może spowodować. To powiedziawszy, jestem bardzo ostrożny, jeśli chodzi o jego używanie.

Moja sytuacja jest taka, że ​​mam dane wejściowe podane przez użytkownika:

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

Gdzie datamapmusi być słownik. Rozejrzałem się i odkryłem, że eval()może to rozwiązać. Pomyślałem, że mógłbym być w stanie sprawdzić typ danych wejściowych przed próbą ich użycia i byłoby to realne zabezpieczenie.

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

Czytałem dokumenty i wciąż jestem niejasny, czy to byłoby bezpieczne, czy nie. Czy eval ocenia dane zaraz po ich wprowadzeniu lub po datamapwywołaniu zmiennej?

Czy astmoduł jest .literal_eval()jedyną bezpieczną opcją?

Odpowiedzi:


190

datamap = eval(raw_input('Provide some data here: '))oznacza, że ​​faktycznie oceniasz kod, zanim uznasz go za niebezpieczny lub nie. Ocenia kod zaraz po wywołaniu funkcji. Zobacz także niebezpieczeństwaeval .

ast.literal_eval zgłasza wyjątek, jeśli dane wejściowe nie są prawidłowym typem danych Pythona, więc kod nie zostanie wykonany, jeśli nie jest.

Używaj ast.literal_evalkiedy tylko potrzebujesz eval. Zwykle nie powinieneś oceniać dosłownych instrukcji Pythona.


20
To nie jest w 100% poprawna rada, ponieważ wszystkie operatory bitowe (lub przeciążone operatory) zawiodą. Na przykład. ast.literal_eval("1 & 1")zgłosi błąd, ale eval("1 & 1")tak się nie stanie.
Daniel van Flymen

1
Po prostu ciekawy. Czy nie powinniśmy używać parserów wyrażeń, czy czegoś podobnego, jeśli oczekujemy czegoś takiego jak „1 & 1”?
thelinuxer

@thelinuxer nadal powinieneś, tak; po prostu nie byłbyś w stanie użyć ast.literal_evaldo czegoś takiego (np. mógłbyś ręcznie zaimplementować parser).
Zmienność

104

ast.literal_eval() uważa tylko niewielki podzbiór składni Pythona za prawidłowy:

Dostarczony ciąg lub węzeł może składać się tylko z następujących struktur literałów Pythona: łańcuchów, liczb, krotek, list, dykt, wartości logicznych i None.

Przejście __import__('os').system('rm -rf /a-path-you-really-care-about')do ast.literal_eval()spowoduje błąd, ale eval()z radością wyczyści dysk.

Ponieważ wygląda na to, że pozwalasz użytkownikowi wprowadzić zwykły słownik, użyj ast.literal_eval(). Bezpiecznie robi to, co chcesz i nic więcej.


obsługuje również ciągi bajtów (bajty klas). Na przykład. b'Hello World '
XChikuX

52

eval: Jest to bardzo potężne, ale jest również bardzo niebezpieczne, jeśli akceptujesz ciągi znaków do oceny z niezaufanych danych wejściowych. Załóżmy, że oceniany ciąg to „os.system ('rm -rf /')”? Naprawdę rozpocznie się usuwanie wszystkich plików z twojego komputera.

ast.literal_eval: Bezpiecznie oceniaj węzeł wyrażenia lub ciąg znaków zawierający literał Pythona lub wyświetlany kontener. Dostarczony ciąg lub węzeł może składać się tylko z następujących struktur literałów Pythona: łańcuchów, bajtów, liczb, krotek, list, dykt, zestawów, wartości logicznych, Brak, bajtów i zestawów.

Składnia:

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

Przykład:

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

W powyższym kodzie ().__class__.__bases__[0]nic poza samym obiektem. Teraz utworzyliśmy instancję wszystkich podklas , tutaj naszym głównym enter code herecelem jest znalezienie z niej jednej klasy o nazwie n .

Musimy codeobiekt i functionobiekt z instancji podklas. Jest to alternatywny sposób CPythonuzyskania dostępu do podklas obiektu i dołączenia systemu.

Od pythona 3.7 metoda ast.literal_eval () jest teraz bardziej rygorystyczna. Dodawanie i odejmowanie dowolnych liczb nie jest już dozwolone. połączyć


1
Używam Pythona 2.7 i właśnie sprawdziłem, czy działa dobrze na Pythonie 3.x. Moja zła, próbowałem go na Pythonie 2.7
Mourya

3
ast.literal_eval("1+1")nie działa w Pythonie 3.7 i jak wspomniano wcześniej, literal_eval powinien być ograniczony do literałów tych kilku struktur danych. Nie powinien być w stanie przeanalizować operacji binarnej.
Sesshu,

Czy mógłbyś wyjaśnić swój KABOOMkod? Znalazłem go tutaj:KABOOM
winklerrr

2
@winklerrr KABOOMjest ładnie wyjaśnione tutaj: nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
Elijas,

41

Python jest chętny do oceny, więc eval(raw_input(...))oceni dane wejściowe użytkownika, gdy tylko trafią eval, niezależnie od tego, co później zrobisz z danymi. Dlatego nie jest to bezpieczne , zwłaszcza podczas evalwprowadzania danych przez użytkownika.

Użyj ast.literal_eval.


Na przykład wpisanie tego w monicie będzie dla ciebie bardzo, bardzo złe:

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

3

Jeśli potrzebujesz tylko słownika dostarczonego przez użytkownika, prawdopodobnie lepszym rozwiązaniem jest json.loads. Głównym ograniczeniem jest to, że json dicts wymaga kluczy łańcuchowych. Możesz również podać tylko dane dosłowne, ale tak jest również w przypadku literal_eval.


1

Utknąłem z ast.literal_eval(). Próbowałem tego w debugerze IntelliJ IDEA i ciągle wracał Nonena wyjściu debuggera.

Ale później, kiedy przypisałem jego wyjście do zmiennej i wydrukowałem w kodzie. Działało dobrze. Przykład kodu udostępniania:

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

Jego wersja Pythona 3.6.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.