evalidate: безопасная обработка пользовательских выражений

Различная фильтрация есть везде. Например, файрволл netfilter (iptables) имеет свой синтаксис для описания пакетов. В файле .htaccess апача свой язык, как определять, кому давать доступ к каталогу, кому нет. В СУБД свой очень мощный язык (SQL WHERE …) для фильтрации записей. В почтовых программах (thunderbird, gmail) — свой интерфейс описания фильтров, в соответствии с которыми письма будут раскидываться по папкам.И везде — свой велосипед.

Для бухгалтерской программы вам может быть удобно позволить пользователю выбрать, кому будет повышена зарплата (все женщины, а так же мужчины возрастом от 25 до 32 лет, либо же до 50 лет если у мужчины имя Вася). И каждому подходящему повысить по пользовательскому выражению (+ 2000 рублей + 20% от прежней зарплаты + по 1000 рублей за каждый год стажа)

Для интернет-магазина (или его админки) — найти все ноутбуки, с памятью от 4 до 8 Gb, которых на складе более 3 штук, но не Acer, или даже Acer, если стоят меньше 30 000 рублей.

Конечно, можно присобачить свою сложную систему фильтров и критериев, сделать для них веб-интерфейс, но проще было бы все сделать в пару строк?

src=»(RAM>=4 and RAM<=8 and stock>3 and not brand=='Acer') or (brand=='Acer' and price<30000)" success, result = evalidate.safeeval(src,notebook) Очевидный способ добавить любую логику в программу — через eval(). Решение самое простое, самое гибкое, но есть большие подводные камни — безопасность. Что если пользовательское выражение будет делать os.system('rm -rf /')?Пример, как можно «завалить» питон через eval():stackoverflow.com/questions/13066594/is-there-a-way-to-secure-strings-for-pythons-evalnedbatchelder.com/blog/201206/eval_really_is_dangerous.htmltav.espians.com/a-challenge-to-break-python-security.html

Часто в советах рекомендуется «правильный путь» — использовать сам питон, чтобы он парсил код из текстовой формы в AST дерево, а потом уже самостоятельно парсить это дерево, отделяя зерна от козлищ. Но как? И тут выходит на арену главная проблема веломаркетинга — пока найдешь подходящий велосипед, или хотя бы хороший чертеж… проще самому велосипед изобрести. Встречайте evalidate, мой маленький велосипед для этой цели. Кому-то может он сам пригодится (я постарался сделать его достаточно гибким), а остальным исходный код может послужить примером, как можно решать эту задачу (ну и как нельзя писать код, конечно же).Ставим пипом

pip install evalidate Простой пример:

import evalidate

depot = [ { 'book': 'Sirens of Titan', 'price': 12, 'stock': 4 }, { 'book': 'Gone Girl', 'price': 9.8, 'stock': 0 }, { 'book': 'Choke', 'price': 14, 'stock': 2 }, { 'book': 'Pulp', 'price': 7.45, 'stock': 4 } ]

#src='stock==0' # books out of stock src='stock>0 and price>8' # expensive book available for sale

for book in depot: success, result = evalidate.safeeval (src, book)

if success: if result: print book else: print «ERR:», result

В данном случае, в src у нас «пользовательский» код, который может быть каким-то плохим. В примере два варианта «хорошего» кода, первый показывает книги, которых у нас нет на складе, второй — дорогие книги, которые в наличии. Если попробовать подсунуть плохой код (просто который не парсится, код с обращением к переменным, которых у нас нет в контексте, код, который использует неразрешенные операции, например Call (вызов функции)), то success будет False, и программа сообщит об ошибке (Но не упадет, и не исполнит плохой код).

Как альтернатива — можно через evalidate.evalidate () получить AST-дерево, которое генерируется через ast.parse (либо exception, если код не парсится или содержит неразрешенные операции), и затем уже его скомпилировать и исполнить через eval ().

node = evalidate.evalidate (src) code = compile (node,'','eval') result = eval (code,{}, data) Ну и еще посмотреть в код модуля (благо он простой), и сделать свой велосипед :-)

В evalidate по умолчанию включен свой набор «безопасных» (?) питоновских операций. Просто, по моему личному мнению — они безопасны. Это значит, что в течение 15 минут мне не пришло в голову, как сделать что-то ужасное, используя только эти операции. Но может быть вам придет? Или, может стоит добавить в список еще какие-то операции, которые сделают дефолтную конфигурацию более гибкой (позволят использовать более богатый язык выражений), и при этом не создадут уязвимостей? Есть идеи?

© Habrahabr.ru