Когда Eval зло в PHP?
За все годы, что я разрабатывал в 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).';');
21 ответ
Я бы с осторожностью назвал eval() чистым злом. Динамическая оценка является мощным инструментом и иногда может спасти жизнь. С помощью eval() можно обойти недостатки PHP (см. Ниже).
Основные проблемы с eval():
- Потенциально небезопасный ввод. Передача ненадежного параметра - способ потерпеть неудачу. Зачастую убедиться, что параметр (или его часть) полностью доверен, не является тривиальной задачей.
- Trickiness. Использование eval() делает код более умным, поэтому его сложнее выполнять. По словам Брайана Кернигана: "Отладка в два раза сложнее, чем писать код в первую очередь. Поэтому, если вы пишете код настолько умно, насколько это возможно, вы, по определению, недостаточно умны, чтобы его отлаживать"
Основная проблема с фактическим использованием eval() только одна:
- Неопытные разработчики, которые используют его без достаточного внимания.
Как правило, я склонен следовать этому:
- Иногда eval() является единственным / правильным решением.
- В большинстве случаев стоит попробовать что-то еще.
- Если не уверены, перейдите к 2.
- Остальное, будь очень, очень осторожен.
eval - это зло, когда существует только малая вероятность того, что userinput включен в вычисляемую строку. Когда вы делаете eval без контента, полученного от пользователя, вы должны быть в безопасности.
Тем не менее, вы должны подумать, по крайней мере, дважды, прежде чем использовать eval, это выглядит обманчиво простым, но с учетом обработки ошибок (см. Комментарий VBAssassins), возможности отладки и т. Д., Это уже не так просто.
Так что, как правило: забудьте об этом. Когда eval является ответом, вы, вероятно, задаете неправильный вопрос!;-)
eval() всегда одинаково злой.
"Когда eval() не является злом?" это неправильный вопрос, на мой взгляд, потому что, похоже, подразумевает, что недостатки использования eval() волшебным образом исчезают в некоторых контекстах.
Использование eval(), как правило, является плохой идеей, поскольку снижает читабельность кода, возможность предсказывать путь к коду (и возможные последствия для безопасности) до выполнения и, следовательно, возможность отладки кода. Использование eval() также может предотвратить оптимизацию оцененного кода и кода, его окружающего, с помощью кэша кода операции, такого как Zend Opcache, интегрированного в PHP 5.5 и выше, или JIT-компилятором, таким как компоновщик в HHVM.
Кроме того, не существует ситуации, для которой абсолютно необходимо использовать eval() - PHP - это полнофункциональный язык программирования без него.
Считаете ли вы это на самом деле злом или можете ли вы лично оправдать использование eval() в некоторых случаях, зависит от вас. Для одних зло слишком велико, чтобы оправдать его, а для других eval() - удобный способ.
Однако, если вы видите eval() как зло, это всегда зло. Он волшебным образом не теряет своего зла в зависимости от контекста.
В этом случае 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];
eval()
медленный, но я бы не назвал это злом.
Это плохое использование, которое мы используем, может привести к внедрению кода и быть злым.
Простой пример:
$_GET = 'echo 5 + 5 * 2;';
eval($_GET); // 15
Вредный пример:
$_GET = 'system("reboot");';
eval($_GET); // oops
Я бы посоветовал вам не использовать eval()
но если вы это сделаете, убедитесь, что вы проверяете / вносите в белый список все данные.
Когда вы используете сторонние данные (например, пользовательский ввод) внутри eval.
В приведенном выше примере это не проблема.
Я откровенно украду содержимое здесь:
Eval по своей природе всегда будет проблемой безопасности.
Помимо проблем безопасности у eval также есть проблема невероятно медленной работы. В моем тестировании на PHP 4.3.10 он в 10 раз медленнее, чем обычный код, и в 28 раз медленнее на PHP 5.1 beta1.
eval()
всегда зло
- по соображениям безопасности
- по соображениям производительности
- для удобства чтения / повторного использования
- по причинам IDE / инструмента
- по причинам отладки
- всегда есть лучший способ
Лично я думаю, что код все еще довольно злой, потому что вы не комментируете то, что он делает. Он также не проверяет свои входные данные на достоверность, что делает его очень хрупким.
Я также чувствую, что, поскольку 95% (или более) видов использования eval активно опасны, небольшая потенциальная экономия времени, которую он может обеспечить в других случаях, не стоит потворствовать плохой практике его использования. Кроме того, вам позже придется объяснить своим миньонам, почему вы используете eval хорошо, а они - плохо.
И, конечно же, ваш PHP выглядит как Perl;)
Есть две ключевые проблемы с eval() (как сценарий "инъекционной атаки"):
1) Может причинить вред 2) Может просто разбиться
и тот, который является скорее социальным, чем техническим:
3) Это соблазнит людей использовать его неуместно в качестве ярлыка в другом месте
В первом случае вы рискуете (очевидно, не когда вы проверяете известную строку) выполнения произвольного кода. Ваши входные данные могут быть не такими известными или фиксированными, как вы думаете.
Скорее всего (в этом случае) вы просто потерпите крах, и ваша строка прекратится с совершенно неясным сообщением об ошибке. ИМХО, весь код должен давать сбой настолько аккуратно, насколько это возможно, в противном случае он должен генерировать исключение (как наиболее поддающаяся обработке форма ошибки).
Я бы предположил, что в этом примере вы кодируете по совпадению, а не по поведению. Да, оператор перечисления SQL (и вы уверены, что перечисление этого поля? - вы называли правильное поле правой таблицы нужной версии базы данных? Он действительно отвечал?) Выглядит как синтаксис объявления массива в PHP, но я бы посоветовал вам не искать кратчайший путь от ввода к выводу, а решить указанную задачу:
- Определите, что у вас есть перечисление
- Извлечь внутренний список
- Распаковать список значений
Это примерно то, что делает ваш вариант, но я бы обернул некоторые if и прокомментировал их для ясности и безопасности (например, если первое совпадение не совпадает, выдает исключение или устанавливает нулевой результат).
Есть все еще некоторые возможные проблемы с экранированными запятыми или кавычками, и вам, вероятно, следует распаковать данные, а затем удалить их из кавычек, но они по крайней мере обрабатывают данные как данные, а не как код.
С preg_version ваш худший результат, вероятно, будет $result=null, а с версией eval худший результат неизвестен, но, по крайней мере, вылет.
Я бы также уделил внимание людям, которые поддерживают ваш код.
eval () не просто смотреть и знать, что должно произойти, ваш пример не так уж плох, но в других местах это может стать настоящим кошмаром.
Это плохое программирование, которое делает eval() злом, а не функцией. Я использую его иногда, так как не могу обойти это в динамическом программировании на нескольких сайтах. Я не могу анализировать PHP на одном сайте, так как я не получу то, что хочу. Я бы просто получил результат! Я счастлив, что функция eval() существует, так как она делает мою жизнь намного проще. User-вход? Только плохие программисты подключаются к хакерам. Я не беспокоюсь об этом.
eval оценивает строку как код, проблема в том, что если строка каким-либо образом "испорчена", она может представлять огромные угрозы безопасности. Обычно проблема в том случае, когда пользовательский ввод оценивается в строке, во многих случаях пользователь может ввести код (например, php или ssi), который затем запускается в eval, он будет работать с теми же разрешениями, что и ваш скрипт php, и может быть использованы для получения информации / доступа к вашему серверу. Может быть довольно сложно убедиться, что пользовательский ввод правильно очищен, прежде чем передать его в eval. Есть и другие проблемы... некоторые из которых являются дискуссионными
PHP советует вам писать свой код таким образом, чтобы он мог выполняться через call_user_func вместо явных уловок.
Это плохое программирование, которое делает eval() злом, а не функцией. Я использую его иногда, так как не могу обойти это в динамическом программировании на нескольких сайтах. Я не могу анализировать PHP на одном сайте, так как я не получу то, что хочу. Я бы просто получил результат! Я счастлив, что функция eval() существует, так как она делает мою жизнь намного проще. User-вход? Только плохие программисты подключаются к хакерам. Я не беспокоюсь об этом.
Я предсказываю, что у вас скоро будут серьезные проблемы...
Честно говоря, абсолютно бесполезно использовать непомерные функции, такие как eval, в интерпретируемом языке, таком как PHP. Я никогда не видел, чтобы eval выполнял программные функции, которые не могли бы быть выполнены другими, более безопасными способами...
Я искренне согласен с тем, что Eval является корнем всего зла для всех людей, которые думают, что тестирование пользовательского ввода поможет. Подумайте дважды, пользовательский ввод может принимать разные формы, и, как мы говорим, хакеры используют эту функцию, которая вас мало заботит. На мой взгляд, просто избегайте eval в целом.
Я видел специально созданные примеры злоупотребления оценочной функцией, которая превосходила мои собственные творческие способности. С точки зрения безопасности, избегайте любой ценой, и я бы даже пошел настолько далеко, что потребовал бы, чтобы это было как минимум вариант в конфигурации PHP, а не "дано".
Другой причиной eval
зло в том, что он не мог кэшироваться кешами байт-кода PHP, такими как eAccelertor или ACP.
Вот решение для запуска кода 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');
По сути, он создает уникальную функцию с включенным кодом в текстовом файле, включает файл, вызывает функцию, а затем удаляет файл, когда завершает работу с ним. Я использую это для ежедневного приема / синхронизации баз данных, где для каждого шага требуется уникальный код для обработки. Это решило все проблемы, с которыми я столкнулся.
Я часто использовал eval(), но я обнаружил, что в большинстве случаев вам не нужно использовать eval для выполнения трюков. Ну, у вас есть call_user_func() и call_user_func_array() в PHP. Это достаточно хорошо, чтобы статически и динамически вызывать любой метод.
Чтобы выполнить статический вызов, создайте свой обратный вызов в виде массива ("имя_класса", "имя_метода") или даже в виде простой строки, например "имя_класса:: имя_метода". Для выполнения динамического вызова используйте обратный вызов в виде массива ($object, 'method').
Единственное разумное использование для eval() - написать собственный компилятор. Я сделал один, но eval все еще злой, потому что его чертовски сложно отлаживать. Хуже всего то, что фатальная ошибка в уклоненном коде приводит к сбою кода, который его вызвал. Я использовал расширение Parsekit PECL, чтобы хотя бы проверить синтаксис, но все равно не радуюсь - попробуйте сослаться на неизвестный класс и все приложения вылетают.
Большинство людей укажут на тот факт, что это может быть опасно, когда вы имеете дело с пользовательским вводом (с которым можно иметь дело).
Для меня хуже всего то, что это снижает ремонтопригодность вашего кода:
- Трудно отлаживать
- Трудно обновить
- Ограничивает использование инструментов и помощников (например, IDE)
Помимо соображений безопасности, eval() не может быть скомпилирован, оптимизирован или кэширован в коде операции, поэтому он всегда будет работать медленнее - медленнее, чем обычный php-код. Таким образом, использование eval нецелесообразно, хотя это не делает его злым. (goto
это зло, eval
это только плохая практика / вонючий код / безобразный)
Другая альтернатива
ссылка: ClosureStream
class ClosureStream
{
const STREAM_PROTO = 'closure';
protected static $isRegistered = false;
protected $content;
protected $length;
protected $pointer = 0;
function stream_open($path, $mode, $options, &$opened_path)
{
$this->content = "<?php\nreturn " . substr($path, strlen(static::STREAM_PROTO . '://')) . ";";
$this->length = strlen($this->content);
return true;
}
public function stream_read($count)
{
$value = substr($this->content, $this->pointer, $count);
$this->pointer += $count;
return $value;
}
public function stream_eof()
{
return $this->pointer >= $this->length;
}
public function stream_set_option($option, $arg1, $arg2)
{
return false;
}
public function stream_stat()
{
$stat = stat(__FILE__);
$stat[7] = $stat['size'] = $this->length;
return $stat;
}
public function url_stat($path, $flags)
{
$stat = stat(__FILE__);
$stat[7] = $stat['size'] = $this->length;
return $stat;
}
public function stream_seek($offset, $whence = SEEK_SET)
{
$crt = $this->pointer;
switch ($whence) {
case SEEK_SET:
$this->pointer = $offset;
break;
case SEEK_CUR:
$this->pointer += $offset;
break;
case SEEK_END:
$this->pointer = $this->length + $offset;
break;
}
if ($this->pointer < 0 || $this->pointer >= $this->length) {
$this->pointer = $crt;
return false;
}
return true;
}
public function stream_tell()
{
return $this->pointer;
}
public static function register()
{
if (!static::$isRegistered) {
static::$isRegistered = stream_wrapper_register(static::STREAM_PROTO, __CLASS__);
}
}
}
ClosureStream::register();
// Your code here!
$closure=include('closure://function(){echo "hola mundo";}');
$closure();
ЭВАЛ В ПОРЯДКЕ,
и пора закрыть эту дискуссию навсегда. (Я знаю, что это старая тема, но да, это происходит и сегодня, и удивительно, как люди почти религиозно отрицают полезную вещь.)
Итак, подумайте об этом:
Канаву включать/требовать?
Когда вы используете какой-либо вид включения, это практическиeval(...)
с файлом. Есть некоторые различия, но если вы настолько безрассудны, что записываете нефильтрованный пользовательский ввод в файл php, а затем включаете его, у вас возникнут проблемы. Теперь скажите мне, что вы хотите прекратить использование include и require.Как ты не знаешь?
Если вы не уверены, может ли строка содержать вредоносный код или нет, отойдите от компьютера. Серьезно, многие вещи могут вам навредить, и если вы не думаете о безопасности 24 часа в сутки, 7 дней в неделю, ваши претензии не к оценке. Подтвердите лучше. Санируйте лучше. Думайте о потенциальных атаках. Хватит стрелять в хороших парней.Самостоятельный анализ
Бывают случаи, когда вы хотите что-то вычислить, например логическое или числовое выражение, решить все скобки и приоритеты, и вы можете использовать eval для выполнения своей работы, но из страха вместо этого пишете свой собственный синтаксический анализатор. Это когда вы выкидываете ребенка, а вся вода из ванны у вас остается. Гораздо проще очистить все, что входит в eval, чем создавать математический анализатор с нуля - часть вычислений вроде5*(12/3+7*(45+12)-65*1)
можно выполнить с помощью простой проверки безопасности регулярных выражений передeval
; теперь давайте посмотрим, как злобный хакер может взломать Пентагон, используя только цифры и скобки. С другой стороны, ваш собственный парсер может вас во многом удивить, и я не говорю, что вы недостаточно хороши, но это довольно далеко не тривиально, даже если вы опытны и родились поляком.
Так как насчет использования eval с осторожностью ?
Вместо того, чтобы называть это неправильным, когда кто-то использует его неправильно.
случай «TLDR»:
Назвать заявления и методы «вредными» — это та самая причина, по которой однажды машины восстанут и маршируют с транспарантами, на которых написано: «ЭТО НЕ НАША ВИНА». И я на их стороне.