Використання python's eval () vs. ast.literal_eval ()?


176

У мене є ситуація з деяким кодом, де він eval()підійшов як можливе рішення. Зараз мені ніколи раніше не довелося користуватися, eval()але я натрапив на багато інформації про потенційну небезпеку, яку вона може спричинити. Це сказав, що я дуже обережно ставлюсь до цього.

Моя ситуація полягає в тому, що я маю дані, які дає користувач:

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

Де datamapповинен бути словник. Я обшукав навколо і виявив, що eval()може це вирішити. Я подумав, що, можливо, я зможу перевірити тип вхідних даних, перш ніж спробувати використовувати дані, і це було б життєздатним запобіжним засобом безпеки.

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

Я читав документи і мені все ще незрозуміло, було б це безпечно чи ні. Чи оцінює eval дані, як тільки їх вводять чи після datamapвиклику змінної?

Є єдиний безпечний варіант astмодуля .literal_eval()?

Відповіді:


190

datamap = eval(raw_input('Provide some data here: '))означає, що ви дійсно оцінюєте код, перш ніж вважаєте його небезпечним чи ні. Він оцінює код, як тільки викликається функція. Дивіться також про небезпекуeval .

ast.literal_eval створює виняток, якщо вхід не є дійсним типом даних Python, тому код не буде виконаний, якщо його немає.

Використовуйте, ast.literal_evalколи вам потрібно eval. Зазвичай не слід оцінювати буквальні твердження Python.


20
Це не 100% правильна порада, оскільки будь-які побітові оператори (або перевантажені оператори) не зможуть. Напр. ast.literal_eval("1 & 1")видасть помилку, але eval("1 & 1")не стане.
Даніель ван Флаймен

1
Просто цікаво. Чи не повинні ми використовувати синтаксичні аналізатори чи щось таке, якщо ми очікуємо чогось типу "1 і 1"?
thelinuxer

@thelinuxer ви все одно повинні, так; ви просто не зможете використати ast.literal_evalщось подібне (наприклад, ви могли реалізувати аналізатор вручну).
Нестабільність

104

ast.literal_eval() справедливим вважає лише невелику підмножину синтаксису Python:

Наданий рядок або вузол може складатися лише з таких буквальних структур Python: рядки, числа, кортежі, списки, дикти, булеві та None.

Якщо пройти __import__('os').system('rm -rf /a-path-you-really-care-about')в систему, ви ast.literal_eval()отримаєте помилку, але eval()радісно стерте диск.

Оскільки, схоже, ви лише дозволяєте користувачеві вводити звичайний словник, використовувати ast.literal_eval(). Це безпечно робить те, що ти хочеш, і більше нічого.


підтримує також рядки байтів (клас байтів). Напр. b'Hello World '
XChikuX

52

eval: Це дуже потужно, але також дуже небезпечно, якщо ви приймаєте рядки для оцінки з ненадійного введення. Припустимо, рядок, який оцінюється, є "os.system ('rm -rf /')"? Це дійсно почне видалення всіх файлів на вашому комп’ютері.

ast.literal_eval: Безпечно оцініть вузол виразів або рядок, що містить літерал Python або відображення контейнера. Наданий рядок або вузол може складатися лише з таких буквальних структур Python: рядки, байти, числа, кортежі, списки, дикти, набори, булеві, None, байти та набори.

Синтаксис:

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

Приклад:

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

У наведеному вище коді ().__class__.__bases__[0]нічого, крім самого об'єкта. Тепер ми створили всі підкласи , і тут наша основна enter code hereмета - знайти один клас з ім'ям n .

Нам потрібно codeзаперечувати та functionоб’єктувати з інстанційованих підкласів. Це альтернативний спосіб CPythonотримати доступ до підкласів об'єкта та приєднати систему.

Від python 3.7 ast.literal_eval () тепер суворіше. Додавання та віднімання довільних чисел заборонено. посилання


1
я використовую python 2.7, і я щойно перевірив його роботу на python 3.x. Моє погано я продовжував пробувати це на python 2.7
Mourya

3
ast.literal_eval("1+1")не працює в python 3.7, і, як було сказано раніше, literal_eval має бути обмежений літералами цих кількох структур даних. Він не повинен мати можливість аналізувати двійкову операцію.
Сесшу

Не могли б ви пояснити свій KABOOMкод? Знайдено його тут:KABOOM
winklerrr

2
@winklerrr KABOOMкрасиво описано тут: nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
Elijas

41

Python прагне в своїй оцінці, тому він eval(raw_input(...))буде оцінювати вхід користувача, як тільки він потрапить eval, незалежно від того, що ви робите з даними згодом. Тому це не безпечно , особливо коли ви evalвводите користувач.

Використовуйте ast.literal_eval.


Наприклад, введення цього запиту буде дуже-дуже погано для вас:

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

3

Якщо все, що вам потрібно, це словник, що надається користувачем, можливе краще рішення json.loads. Основне обмеження полягає в тому, що диктові json потрібні рядкові клавіші. Також ви можете надати лише буквальні дані, але це теж стосується literal_eval.


1

Я застряг ast.literal_eval(). Я пробував це в налагоджувачі IntelliJ IDEA, і він постійно повертався Noneна виході налагоджувача.

Але пізніше, коли я призначив його вихід змінній і надрукував її в коді. Це добре працювало. Приклад спільного коду:

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

Його версія python 3.6.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.