Когда eval Evil в PHP?

84

За все годы разработки php я всегда слышал, что использование eval()- зло.

Учитывая следующий код, не имеет ли смысла использовать второй (и более элегантный) вариант? Если нет, то почему?

// $type is the result of an SQL statement
// e.g. SHOW COLUMNS FROM a_table LIKE 'a_column';
// hence you can be pretty sure about the consistency
// of your string
$type = "enum('a','b','c')";

// possibility one
$type_1 = preg_replace('#^enum\s*\(\s*\'|\'\s*\)\s*$#', '', $type);
$result = preg_split('#\'\s*,\s*\'#', $type_1);

// possibility two
eval('$result = '.preg_replace('#^enum#','array', $type).';');
Пьер Спринг
источник
2
eval ВСЕГДА является злом, всегда есть лучший способ написать код, особенно с тех пор, как PHP представил анонимные функции. В этом случае я бы использовал$result = array(); preg_replace_callback('#^enum\s*\(\s*\'|\'\s*\)\s*$#', function($m) use($result) { $result[] = $m[1]; }, $type);
Джеффри
Честно говоря, я думаю, что главная проблема php не в языке, а в людях, которые его используют. Все три правильных ответа на этот вопрос (thomasrutter's, braincracking's и мой) получили отрицательные голоса, и никто не имел против них никаких очков. С другой стороны, в одном из ответов утверждается, что «Иногда eval () - единственное / правильное решение» без примеров или объяснений, и за него проголосовало
Франсуа Буржуа

Ответы:

134

Я бы с осторожностью назвал eval () чистым злом. Динамическая оценка - мощный инструмент, который иногда может спасти жизнь. С eval () можно обойти недостатки PHP (см. Ниже).

Основные проблемы с eval ():

  • Возможный небезопасный ввод. Передача ненадежного параметра - это способ потерпеть неудачу. Часто бывает нетривиальной задачей убедиться, что параметр (или его часть) полностью доверяет.
  • Ловкость. Использование eval () делает код умным, поэтому его труднее отслеживать. Цитируя Брайана Кернигана: « Отладка в два раза сложнее, чем написание кода в первую очередь. Поэтому, если вы пишете код настолько умно, насколько это возможно, вы по определению недостаточно умны, чтобы отлаживать его ».

Основная проблема с фактическим использованием eval () только одна:

  • Неопытные разработчики, которые используют его без должного внимания.

Как правило, я придерживаюсь следующего:

  1. Иногда eval () - единственное / правильное решение.
  2. В большинстве случаев следует попробовать что-то другое.
  3. Если не уверены, перейдите к 2.
  4. В противном случае будьте очень и очень осторожны.
Михал Рудницкий
источник
4
Это можно было бы отредактировать, чтобы избежать eval (), особенно если $ className занесено в белый список, что должно быть, чтобы можно было использовать eval (). Однако хорошие новости заключаются в том, что, начиная с версии 5.3, $ foo :: bar () действует.
Rojoca 04
@rojoca: Не могли бы вы привести пример, как это сделать без eval () в PHP 5.2?
Michał Rudnicki
30
Я не уверен, считается ли он eval или нет, но $ result = call_user_func (array ('Foo', 'bar')); работает как шарм.
Ionuț G. Stan
3
Хороший момент по поводу хитрости - в вашем (простом) примере переменная появляется «из ниоткуда». Если код становится немного более сложным, удачи следующему человеку, который посмотрит на код и попытается отыскать эту переменную (был там, сделал это, все, что у меня было, это головная боль и эта паршивая футболка).
Писквор покинул здание
Небезопасный ввод можно избежать
thepowerlies
40

eval является злом, когда существует лишь малейшая вероятность того, что ввод пользователя будет включен в вычисляемую строку. Когда вы выполняете eval без содержимого, полученного от пользователя, вы должны быть в безопасности.

Тем не менее, вы должны подумать как минимум дважды, прежде чем использовать eval, это выглядит обманчиво простым, но с обработкой ошибок (см. Комментарий VBAssassins), возможностью отладки и т. Д. Это уже не так просто.

Итак, практическое правило: забудьте об этом. Когда eval - это ответ, вы, вероятно, задаете неправильный вопрос! ;-)

Патрик Корнелиссен
источник
6
Иногда ответ - это eval. Мы работаем над онлайн-игрой, и там очень сложно избежать evals из-за очень сложных отношений между сущностями ... но IMHO в 90% случаев eval не является ответом.
Jet
19
Мне было бы очень любопытно увидеть ситуацию, в которой ответом будет ТОЛЬКО eval.
Ionuț G. Stan
2
@Ionut G. Stan Пользовательские триггеры, хранящиеся в базе данных для объектов / сущностей?
Kuroki Kaze
8
Я действительно не верю, что любое из этих оправданных применений eval (). В каждом случае сделать то же самое без использования eval () по крайней мере возможно. Это не значит, что PHP не работает без eval (). Конечно, eval () - это ярлык в подобных случаях, но он по-прежнему делает ваш путь кода немного сложнее, а проблемы немного сложнее отлаживать. Это мое мнение.
thomasrutter 07
4
@Christian: ВАМ следует сначала написать пример (используйте pastebin.com и вставьте сюда ссылку), где, по вашему мнению, избежать использования eval()невозможно, это лучший подход. Многие говорят, eval()что в некоторых случаях это неизбежно, но они не приводят никаких конкретных примеров, которые мы могли бы спорить - таким образом, эта дискуссия не имеет смысла. Так что, люди, которые говорят, что eval()это неизбежно, сначала докажите это!
Sk8erPeter
19

eval всегда одинаково "злой".

Если вы считаете eval () злом, это всегда зло. Он не теряет магической злобы в зависимости от контекста.

На мой взгляд, вопрос «когда eval () не является злом?» похоже, подразумевает, что недостатки использования eval () волшебным образом исчезают в некоторых контекстах.

Использование eval (), как правило, является плохой идеей, потому что оно снижает читаемость кода, способность предсказывать путь кода (и возможные последствия этого для безопасности) до выполнения и, следовательно, ухудшает способность анализировать и отлаживать код. Использование eval () также может предотвратить оптимизацию оцениваемого кода и окружающего его кода с помощью кеша опкодов, такого как Zend Opcache, интегрированного в PHP 5.5 и выше, или JIT-компилятором, например, в HHVM.

Более того, нет ситуации, в которой абсолютно необходимо использовать eval () - PHP - это полноценный язык программирования без него. Для чего бы вы ни хотели использовать eval (), это еще один способ сделать это.

Считаете ли вы это злом или можете лично оправдать использование eval () в некоторых случаях, зависит от вас. Для некоторых зло слишком велико, чтобы его оправдать, а для других eval () - удобный ярлык.

Thomasrutter
источник
2
Из вас получился адский программист.
Christian
1
«Более того, нет ситуации, в которой абсолютно необходимо использовать eval ()» - Что насчет того, если я хочу выполнить код, который я храню в базе данных, в соответствии с примером, приведенным в документации PHP?
Ярин
4
На это я бы сказал, что хранить PHP-код в базе данных одинаково плохо и в равной степени ненужно. Чего бы вы ни хотели достичь, есть другие способы сделать это, кроме хранения PHP в базе данных или использования eval (). Сделайте это, если хотите, и если это полезный ярлык для вас, но если вы относитесь к eval () как к злу, то вам, вероятно, придется относиться к хранению PHP в базе данных как к злу.
thomasrutter
13
Он добавляет еще один вектор атаки - SQL-инъекция может также запускать произвольный код PHP на веб-сервере. Это требует использования eval (). Его нельзя оптимизировать с помощью кешей байт-кода. Это нарушает принцип разделения кода и данных. Это может сделать ваше приложение менее переносимым - для обновления / исправления PHP вам также необходимо обновить базу данных.
thomasrutter
3
Я бы сказал, что если чего-то можно достичь только с помощью eval, было бы неплохо пересмотреть решение о том, чтобы что-то сделать. Динамическое создание классов во время выполнения кажется плохой идеей. Почему бы не использовать некоторую генерацию кода и не сгенерировать код, который определяет их заранее?
thomasrutter
15

В этом случае eval, вероятно, достаточно безопасен, поскольку пользователь никогда не сможет создать произвольные столбцы в таблице.

Хотя это не совсем элегантно. В основном это проблема парсинга текста, и злоупотребление парсером PHP для обработки кажется немного взломанным. Если вы хотите злоупотребить языковыми функциями, почему бы не злоупотребить парсером JSON? По крайней мере, с парсером JSON нет никакой возможности внедрения кода.

$json = str_replace(array(
    'enum', '(', ')', "'"), array)
    '',     '[', ']', "'"), $type);
$result = json_decode($json);

Регулярное выражение, вероятно, является наиболее очевидным способом. Вы можете использовать одно регулярное выражение для извлечения всех значений из этой строки:

$extract_regex = '/
    (?<=,|enum\()   # Match strings that follow either a comma, or the string "enum("...
    \'      # ...then the opening quote mark...
    (.*?)       # ...and capture anything...
    \'      # ...up to the closing quote mark...
    /x';
preg_match_all($extract_regex, $type, $matches);
$result = $matches[1];
BlackAura
источник
1
хотя это не дает ответа на вопрос как таковой, это очень хороший ответ на вопрос: как лучше всего разобрать enum ()… спасибо;)
Пьер Спринг
13

eval() медленно, но я бы не назвал это злом.

Это плохое использование, которое мы делаем, что может привести к инъекции кода и быть злом.

Простой пример:

$_GET = 'echo 5 + 5 * 2;';
eval($_GET); // 15

Вредный пример:

$_GET = 'system("reboot");';
eval($_GET); // oops

Я бы посоветовал вам не использовать, eval()но если вы это сделаете, убедитесь, что вы подтвердили / занесли в белый список все входные данные.

Аликс Аксель
источник
12

Когда вы используете сторонние данные (например, пользовательский ввод) внутри eval.

В приведенном выше примере это не проблема.

GreenieMeanie
источник
7

Я явно украду содержимое здесь:

  1. Eval по своей природе всегда будет проблемой для безопасности.

  2. Помимо проблем безопасности, eval также невероятно медленный. В моем тестировании на PHP 4.3.10 он в 10 раз медленнее обычного кода и в 28 раз медленнее на PHP 5.1 beta1.

blog.joshuaeichorn.com: использование-eval-in-php

Stefs
источник
5

eval()это всегда зло.

  • по соображениям безопасности
  • по соображениям производительности
  • для удобства чтения / повторного использования
  • по причинам IDE / инструмента
  • по причинам отладки
  • всегда есть лучший способ
Франсуа Буржуа
источник
@bracketworks: Но вы ошибаетесь - все эти присущие проблемы не исчезают волшебным образом в некоторых ситуациях. Зачем им? Возьмем, к примеру, производительность: вы действительно думаете, что интерпретатор будет выполнять чрезвычайно медленную функцию eval () с повышенной скоростью только потому, что вы использовали ее каким-то мистическим «не-злым» способом? Или эта пошаговая отладка будет работать внутри вашего eval-предложения в какой-нибудь удачный день?
Francois Bourgeois
3
Честно говоря, я думаю, что ответ @MichałRudnicki лучше всего говорит об этом. Не поймите меня неправильно, всякий раз, когда я задаю ( себе ) вопрос и eval()получаю ответ , я сразу же предполагаю, что задал вопрос неправильно; редко это «правильный» ответ, но для общего утверждения, что это всегда зло, просто неверно.
Дэн Лагг
если я хочу сохранить код в базе данных? как я могу это выполнить?
Константин XFlash Stratigenas
@KonstantinXFlashStratigenas Вы должны спросить себя, почему вы храните исполняемый код в базе данных. База данных предназначена для данных, полученных из вашего кода, а не из вашего кода. По крайней мере, вы увеличиваете векторы атаки.
Jack B
4

Я бы также уделил внимание людям, поддерживающим ваш код.

eval () - это не простой способ просто посмотреть и узнать, что должно произойти, ваш пример не так уж плох, но в других местах это может быть кошмаром.


источник
4

Лично я считаю, что этот код по-прежнему довольно злой, потому что вы не комментируете то, что он делает. Он также не проверяет свои входные данные на достоверность, что делает его очень хрупким.

Я также считаю, что, поскольку 95% (или больше) случаев использования eval являются активно опасными, небольшая потенциальная экономия времени, которую он может обеспечить в других случаях, не стоит того, чтобы отказываться от плохой практики его использования. Кроме того, позже вам придется объяснять своим миньонам, почему вы используете eval, а они - плохо.

И, конечно же, ваш PHP выглядит как Perl;)

Есть две ключевые проблемы с eval () (как сценарий "инъекционной атаки"):

1) Это может причинить вред 2) Может просто вылететь

и более социальный, чем технический:

3) Это будет соблазнять людей использовать его ненадлежащим образом в качестве ярлыка в другом месте

В первом случае вы рискуете (очевидно, не когда вы оцениваете известную строку) выполнения произвольного кода. Однако ваши входные данные могут быть не такими известными или фиксированными, как вы думаете.

Скорее всего (в этом случае) вы просто упадете, и ваша строка закончится неоправданно непонятным сообщением об ошибке. IMHO, весь код должен выходить из строя настолько аккуратно, насколько это возможно, в противном случае он должен вызывать исключение (как наиболее управляемую форму ошибки).

Я бы предположил, что в этом примере вы кодируете случайно, а не кодируете поведение. Да, оператор перечисления SQL (и вы уверены, что перечисление этого поля? - вы вызывали правильное поле правильной таблицы правильной версии базы данных? Действительно ли он ответил?) Выглядит как синтаксис объявления массива в PHP, но я бы посоветовал вам действительно не найти кратчайшего пути от ввода к выводу, а скорее заняться указанной задачей:

  • Определите, что у вас есть перечисление
  • Извлеките внутренний список
  • Распаковать значения списка

Это примерно то, что делает ваш вариант, но я бы обернул некоторые if и комментарии вокруг него для ясности и безопасности (например, если первое совпадение не совпадает, выбросить исключение или установить нулевой результат).

По-прежнему существуют некоторые возможные проблемы с экранированными запятыми или кавычками, и вам, вероятно, следует распаковать данные, а затем исключить их из кавычек, но он, по крайней мере, обрабатывает данные как данные, а не как код.

С preg_version ваш худший результат, вероятно, будет $ result = null, с версией eval худший неизвестен, но по крайней мере сбой.

Фаза синтаксического анализа
источник
3

eval оценивает строку как код, проблема в том, что если строка каким-либо образом "испорчена", это может привести к огромным угрозам безопасности. Обычно проблема возникает в случае, когда ввод пользователя оценивается в строке, во многих случаях пользователь может ввести код (например, php или ssi), который затем запускается в eval, он будет работать с теми же разрешениями, что и ваш сценарий php, и может использоваться для получения информации / доступа к вашему серверу. Может быть довольно сложно убедиться, что пользовательский ввод правильно очищен, прежде чем передать его eval. Есть и другие проблемы ... некоторые из которых спорны

Тоби
источник
3

PHP советует писать код таким образом, чтобы он мог выполняться через call_user_func вместо явных вычислений.

Нольте
источник
2

Другая причина evalзла заключается в том, что он не может кэшироваться кешами байт-кода PHP, такими как eAccelertor или ACP.

Бегемот
источник
2

Плохое программирование делает eval () злом, а не функцию. Я иногда его использую, так как не могу обойтись без него при динамическом программировании на нескольких сайтах. Я не могу проводить синтаксический анализ PHP на одном сайте, так как я не получу то, что хочу. Я бы просто получил результат! Я счастлив, что существует функция eval (), поскольку она делает мою жизнь намного проще. Пользовательский ввод? Хакеры подключают только плохих программистов. Я не беспокоюсь об этом.

Игорь М. - PortalPress.org
источник
2

Плохое программирование делает eval () злом, а не функцию. Я иногда его использую, так как не могу обойтись без него при динамическом программировании на нескольких сайтах. Я не могу проводить синтаксический анализ PHP на одном сайте, так как я не получу то, что хочу. Я бы просто получил результат! Я счастлив, что существует функция eval (), поскольку она делает мою жизнь намного проще. Пользовательский ввод? Хакеры подключают только плохих программистов. Я не беспокоюсь об этом.

Я предсказываю, что скоро у тебя возникнут серьезные проблемы ...

Честно говоря, в интерпретируемом языке, таком как PHP, совершенно бесполезно использовать такую ​​непомерную функцию, как eval. Я никогда не видел, чтобы eval выполнял программные функции, которые нельзя было бы выполнить другими, более безопасными способами ...

Я полностью согласен с тем, что Eval - это корень всех зол для всех людей, которые думают, что тестирование пользовательского ввода поможет. Подумайте дважды, пользовательский ввод может принимать самые разные формы, и, пока мы говорим, хакеры используют эту функцию, о которой вы не заботились. На мой взгляд, просто избегайте eval вообще.

Я видел созданные примеры злоупотребления функцией eval, превосходящие мои собственные творческие способности. С точки зрения безопасности, избегайте любой ценой, и я бы даже пошел так далеко, что потребовал бы, чтобы это было как минимум опцией в конфигурации PHP, а не «заданным».

Braincracking
источник
Рассуждение о том, что «как бы тщательно вы ни старались защитить свой код в отношении функции X, умные хакеры всегда на шаг впереди ...» можно применить к любой другой технике, а не только к X == eval. И поэтому для любой функции X: X является корнем всех eval ... э ... зла, так что лучше вообще отказаться от программирования (таким образом вытаскивая ковер из-под этих глупых хакеров, но в конечном итоге все же перехитрив их).
Sz.
«нет абсолютно бесполезного применения такой непомерной функции, как eval» -> любой, кто может использовать слова вроде «абсолютно», либо новичок в программировании, либо плохой программист.
unity100
2

Вот решение для запуска кода PHP, извлеченного из базы данных, без использования eval. Разрешает использование всех функций и исключений в области видимости:

$rowId=1;  //database row id
$code="echo 'hello'; echo '\nThis is a test\n'; echo date(\"Y-m-d\");"; //php code pulled from database

$func="func{$rowId}";

file_put_contents('/tmp/tempFunction.php',"<?php\nfunction $func() {\n global \$rowId;\n$code\n}\n".chr(63).">");

include '/tmp/tempFunction.php';
call_user_func($func);
unlink ('/tmp/tempFunction.php');

По сути, он создает уникальную функцию с включенным кодом в текстовом файле, включает файл, вызывает функцию, а затем удаляет файл, когда с ней закончено. Я использую это для выполнения ежедневных загрузок / синхронизаций базы данных, где каждый шаг требует уникального кода для обработки. Это решило все проблемы, с которыми я столкнулся.

Джейсон
источник
Это похоже на ответ на ответ; пожалуйста, опубликуйте это как таковое, когда сможете.
rfornal 02
1

Раньше я часто использовал eval (), но обнаружил, что в большинстве случаев вам не нужно использовать eval для выполнения трюков. Что ж, у вас есть call_user_func () и call_user_func_array () в PHP. Достаточно статически и динамически вызывать любой метод.

Чтобы выполнить статический вызов, создайте обратный вызов как массив ('class_name', 'method_name') или даже как простую строку, например, 'class_name :: method_name'. Для выполнения динамического вызова используйте обратный вызов в стиле array ($ object, 'method').

Единственное разумное использование eval () - это написать собственный компилятор. Я сделал один, но eval по-прежнему злой, потому что его чертовски сложно отлаживать. Хуже всего то, что фатальная ошибка в исключенном коде приводит к сбою кода, который ее вызвал. Я использовал расширение Parsekit PECL, по крайней мере, чтобы проверить синтаксис, но все равно без радости - попробуйте сослаться на сбой неизвестного класса и всего приложения.

Гарри
источник
0

Помимо проблем безопасности, eval () не может быть скомпилирован, оптимизирован или кэширован, поэтому он всегда будет медленнее - намного медленнее - чем обычный PHP-код. Таким образом, использование eval неэффективно, хотя это не делает его злом. ( gotoзло, evalтолько плохая практика / вонючий код / ​​уродливый)

Арон Седерхольм
источник
evalполучает более низкий рейтинг зла, чем goto? Это противоположный день?
JLRishe
Просто я держу обиду на разработчиков php за то, что они фактически реализовали gotoв 5.3.
Арон Седерхольм
1
Не-а, gotoфобианам: есть инструменты, а есть мастера, которые ими пользуются (или нет). Ни разу не инструменты , которые делают профессиональный вид , как на идиота, когда делать ошибки, а неспособность использовать соответствующие инструменты (должным образом). Но всегда виноваты инструменты ...
Sz.
0

Большинство людей укажут на тот факт, что это может быть опасно, когда вы имеете дело с пользовательским вводом (с которым можно иметь дело).

Для меня хуже всего то, что это снижает ремонтопригодность вашего кода:

  • Трудно отлаживать
  • Трудно обновить
  • Ограничивает использование инструментов и помощников (например, IDE)
силы
источник