Почему следует избегать exec() и eval()?
Я видел это несколько раз в разных местах, но никогда не находил удовлетворительного объяснения, почему это так.
Так что, надеюсь, один будет представлен здесь. Почему бы нам (по крайней мере, вообще) не использовать exec()
а также eval()
?
РЕДАКТИРОВАТЬ: я вижу, что люди предполагают, что этот вопрос относится к веб-серверам - это не так. Я могу понять, почему неанизированная строка передается exec
может быть плохо Это плохо в не-веб-приложениях?
11 ответов
Часто есть более четкие, более прямые способы получить тот же эффект. Если вы соберете сложную строку и передадите ее в exec, то будет сложно следовать коду и проверить его.
Пример: я написал код, который считывает строковые ключи и значения и устанавливает соответствующие поля в объекте. Это выглядело так:
for key, val in values:
fieldName = valueToFieldName[key]
fieldType = fieldNameToType[fieldName]
if fieldType is int:
s = 'object.%s = int(%s)' % (fieldName, fieldType)
#Many clauses like this...
exec(s)
Этот код не так уж страшен для простых случаев, но с появлением новых типов он становится все более и более сложным. Когда были ошибки, они всегда срабатывали при вызове exec, поэтому трассировка стека не помогла мне их найти. В конце концов я переключился на немного более длинную, менее умную версию, которая явно устанавливала каждое поле.
Первое правило ясности кода заключается в том, что каждая строка вашего кода должна быть проста для понимания, если смотреть только на строки рядом с ней. Вот почему goto и глобальные переменные не приветствуются. exec и eval позволяют легко нарушить это правило.
Когда вам нужны exec и eval, да, вы действительно нуждаетесь в них.
Но большинство использования этих функций (и аналогичных конструкций в других языках сценариев) совершенно неуместно и может быть заменено другими более простыми конструкциями, которые быстрее, безопаснее и содержат меньше ошибок.
Вы можете при правильном экранировании и фильтрации безопасно использовать exec и eval. Но тот тип кодировщика, который идет прямо к exec / eval для решения проблемы (потому что они не понимают других возможностей, предоставляемых языком), не тот тип кодировщика, который сможет выполнить эту обработку правильно; это будет тот, кто не понимает обработку строк и просто слепо объединяет подстроки, что приводит к хрупкому небезопасному коду.
Это Приманка Струн. Бросать строковые сегменты вокруг выглядит легко и обманывает наивных программистов, заставляя их думать, что они понимают, что делают. Но опыт показывает, что результаты почти всегда неверны в каком-то "угловом" (или не очень) случае, часто с потенциальными последствиями для безопасности. Вот почему мы говорим, что Eval - это зло. Вот почему мы говорим, что регулярное выражение для HTML - это зло. Вот почему мы продвигаем параметризацию SQL. Да, вы можете сделать все это правильно с ручной обработкой строк... но если вы уже не понимаете, почему мы говорим такие вещи, скорее всего, вы этого не сделаете.
eval() и exec() могут способствовать ленивому программированию. Что еще более важно, это означает, что исполняемый код, возможно, не был написан во время разработки, поэтому не проверен Другими словами, как вы тестируете динамически сгенерированный код? Особенно в браузерах.
Безопасность в стороне, eval
а также exec
часто отмечаются как нежелательные из-за сложности, которую они вызывают. Когда вы видите eval
Вы часто звоните, не зная, что на самом деле происходит за этим, потому что это влияет на данные, которые обычно находятся в переменной. Это затрудняет чтение кода.
Вызвать всю мощь переводчика - это тяжелое оружие, которое должно быть зарезервировано только для очень сложных случаев. Однако в большинстве случаев этого лучше избегать, и следует использовать более простые инструменты.
Тем не менее, как и все обобщения, будьте осторожны с этим. В некоторых случаях exec и eval могут быть полезны. Но у вас должна быть очень веская причина использовать их. Смотрите этот пост для одного приемлемого использования.
В отличие от того, что говорится здесь в большинстве ответов, exec фактически является частью рецепта для создания супер-полных декораторов в Python, так как вы можете точно дублировать все в оформленной функции, создавая одну и ту же сигнатуру для целей документации и тому подобного. Это ключ к функциональности широко используемого модуля декоратора ( http://pypi.python.org/pypi/decorator/). Другие случаи, когда exec / eval важны, - это при создании любого приложения типа "интерпретируемый Python", такого как язык шаблонов с синтаксическим анализом Python (например, Mako или Jinja).
Так что не похоже, что наличие этих функций является прямым признаком "небезопасного" приложения или библиотеки. Использование их наивным способом javascripty для оценки входящего JSON или чего-то еще, да, это очень небезопасно. Но, как всегда, все в том, как вы его используете, и это очень важные функции.
Я использовал eval()
в прошлом (и до сих пор время от времени) для массирования данных во время быстрых и грязных операций. Он является частью инструментария, который можно использовать для выполнения работы, но НИКОГДА его не следует использовать для всего, что вы планируете использовать в производстве, например для инструментов командной строки или сценариев, по всем причинам, упомянутым в других ответах.
Вы не можете доверять своим пользователям - когда-либо - делать правильные вещи. В большинстве случаев они это сделают, но вы должны ожидать, что они сделают все то, о чем вы никогда не думали, и обнаружите все ошибки, которые вы никогда не ожидали. Это именно то, где eval()
превращается из инструмента в пассив.
Прекрасным примером этого будет использование Django при построении QuerySet
, Параметры, передаваемые в запрос, принимают ключевые аргументы, которые выглядят примерно так:
results = Foo.objects.filter(whatever__contains='pizza')
Если вы программно присваиваете аргументы, вы можете подумать сделать что-то вроде этого:
results = eval("Foo.objects.filter(%s__%s=%s)" % (field, matcher, value))
Но всегда есть лучший способ, который не использует eval()
, который передает словарь по ссылке:
results = Foo.objects.filter( **{'%s__%s' % (field, matcher): value} )
Делая это таким образом, это не только быстрее с точки зрения производительности, но также и безопаснее и более Pythonic.
Мораль истории?
Использование eval()
подходит для небольших задач, тестов и действительно временных задач, но плохо для постоянного использования, потому что почти наверняка всегда есть лучший способ сделать это!
По той же причине, по которой вы не должны входить в систему как root: слишком легко выстрелить себе в ногу.
Разрешение этих функций в контексте, где они могут выполнять пользовательский ввод, является проблемой безопасности, и дезинфицирующие средства, которые на самом деле работают, сложно написать.
s = "import shutil; shutil.rmtree('/nonexisting')"
eval(s)
Теперь предположим, что кто-то может управлять s из веб-приложения, например.
Не пытайтесь сделать это на вашем компьютере
Причина № 1: Один недостаток безопасности (т. Е. Ошибки программирования... и мы не можем утверждать, что их можно избежать), и вы только что предоставили пользователю доступ к оболочке сервера.
Попробуйте это в интерактивном переводчике и посмотрите, что произойдет:
>>> import sys
>>> eval('{"name" : %s}' % ("sys.exit(1)"))
Конечно, это угловой случай, но может быть сложно предотвратить подобные вещи.