Коли eval зло в 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 () є єдиним / правильним рішенням" без прикладу чи пояснення, і за це отримують найбільшу ...
Франсуа Буржуа

Відповіді:


133

Я був би обережним, називаючи eval () чистим злом. Динамічне оцінювання є потужним інструментом і іноді може врятувати життя. За допомогою eval () можна усунути недоліки PHP (див. Нижче).

Основними проблемами eval () є:

  • Потенційно небезпечне введення. Передача ненадійного параметра - це спосіб невдачі. Переконатися, що якийсь параметр (або його частина) є повністю довіреним, часто не є тривіальним завданням.
  • Хитрість. Використання eval () робить код розумним, тому його важче дотримуватися. Цитуючи Брайана Кернігана, " налагодження вдвічі складніше, ніж написання коду. Тому, якщо ви пишете код якомога розумніше, ви, за визначенням, недостатньо розумні, щоб налагодити його "

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

  • Недосвідчені розробники, які використовують його без достатньої уваги.

Як правило, я схильний дотримуватися цього:

  1. Іноді eval () є єдиним / правильним рішенням.
  2. У більшості випадків слід спробувати щось інше.
  3. Якщо ви не впевнені, перейдіть до 2.
  4. В іншому, будь дуже, дуже обережним.

4
Це можна змінити, щоб також уникнути eval (), особливо якщо $ className внесено до білого списку, яким він повинен бути, щоб розважати використання eval (). Хороші новини, однак, станом на 5.3, $ foo :: bar () є дійсним.
rojoca

@rojoca: Чи можете ви навести нам приклад, як це зробити без eval () у PHP 5.2, будь ласка?
Міхал Рудніцький,

30
Я не впевнений, чи вважається це eval чи ні, але $ result = call_user_func (array ('Foo', 'bar')); працює як шарм.
Ionuț G. Stan

3
Хороший момент щодо хитрості - у вашому (простому) прикладі змінна вискакує "з нізвідки". Якщо код стає трохи складнішим, удачі наступній людині, яка подивиться на код і спробує переслідувати цю змінну (там, зробив це, все, що у мене було, це головний біль і ця паршива футболка).
Пісквор залишив будівлю

Небезпечного введення можна уникнути
thepowerlies

40

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

Тим не менше, вам слід принаймні двічі подумати перед тим, як використовувати eval, він виглядає обманливо простим, але з урахуванням помилок (див. Коментар VBAssassins), налагодження тощо, це вже не так просто.

Отже, як правило: забудьте про це. Коли eval - це відповідь, ви, можливо, ставите неправильне запитання! ;-)


6
Іноді eval - це відповідь. Ми працюємо над онлайн-ігровим додатком, і нам дуже важко уникнути перепадів через дуже складні відносини між організаціями ... але IMHO у 90% випадків eval не є відповіддю.
Реактивний

19
Мені було б дуже цікаво побачити ситуацію, коли ТІЛЬКИ eval - це відповідь.
Ionuț G. Stan,

2
@Ionut G. Stan Зберігаються в базі даних спеціальні тригери для об’єктів / сутностей?
Курокі Казе,

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

4
@Christian: ВИ повинні спершу написати приклад (скористайтеся pastebin.com і вставте посилання сюди), де, на вашу думку, уникати використання eval()неможливо, це кращий підхід. Багато людей кажуть eval(), що в деяких випадках це неминуче, але вони не згадують жодних конкретних прикладів, з якими ми могли б посперечатися - таким чином ця дискусія не має сенсу. Тож будь-ласка, люди, які кажуть, що eval()це неминуче, докажіть це спочатку!
Sk8erPeter

18

eval () - однаково злий у всі часи.

"Коли eval () не є злом?" на мою думку, це неправильне запитання, оскільки це, мабуть, означає, що недоліки використання eval () магічним чином зникають у деяких контекстах.

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

Крім того, немає жодної ситуації, для якої вкрай необхідно використовувати eval () - PHP є повноцінною мовою програмування без нього.

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

Однак якщо ви бачите eval () як зло, це зло в усі часи. Він не втрачає магічно свою злобу залежно від контексту.


1
Ви з пекла програміст.
Крістіан

1
"Крім того, немає ситуації, для якої абсолютно необхідно використовувати eval ()" - А як щодо того, якщо я хочу виконати код, який я зберігав у базі даних, на прикладі, наведеному в документах PHP?
Ярін,

4
На це я б сказав, що зберігати PHP-код у базі даних однаково зло і однаково непотрібно. Що б ви не хотіли досягти, існують інші способи, ніж зберігання PHP у базі даних або використання eval (). Зробіть це, якщо хочете, і якщо це корисний ярлик для вас, але якщо ви ставитеся до eval () як до зла, то вам, мабуть, доведеться ставитися до зберігання PHP у базі даних також як до зла.
thomasrutter

12
Він додає ще один вектор атаки - ін'єкція 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];

1
навіть незважаючи на те, що це не дало відповіді на запитання як таке, це дуже гарна відповідь на запитання: який найкращий спосіб проаналізувати перерахування ()… дякую;)
П’єр Спрінг

13

eval() це повільно, але я б не назвав це злом.

Це погане використання, яке ми використовуємо, що може призвести до введення коду і бути злим.

Простий приклад:

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

Шкідливий приклад:

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

Я б радив вам не використовувати, eval()але якщо ви це зробите, переконайтеся, що ви перевірили / додали до білого списку всі введені дані.


12

Коли ви використовуєте сторонні дані (наприклад, введення користувачем) всередині eval.

У наведеному вище прикладі це не проблема.


7

я відверто викраду вміст тут:

  1. Eval за своєю природою завжди буде турбувати безпеку.

  2. Окрім проблем безпеки, у eval також є проблема неймовірно повільної роботи. У моєму тестуванні на PHP 4.3.10 це в 10 разів повільніше, ніж звичайний код, і в 28 разів повільніше на PHP 5.1 beta1.

blog.joshuaeichorn.com: using-eval-in-php


5

eval()це завжди зло.

  • з міркувань безпеки
  • з міркувань продуктивності
  • з причини читабельності / повторного використання
  • з IDE / інструментальних причин
  • з причин налагодження
  • завжди є кращий спосіб

@bracketworks: Але ви помиляєтесь - у цих ситуаціях усі ці невід’ємні проблеми магічним чином не зникають. Чому вони повинні? Візьмемо для прикладу продуктивність: Ви справді думаєте, що інтерпретатор буде виконувати надзвичайно повільну функцію eval () з підвищеною швидкістю лише тому, що Ви використовували її якось містично "не зле"? Або що поетапна налагодження буде працювати у вашому eval-clause в якийсь щасливий день?
Франсуа Буржуа

3
Чесно кажучи, я думаю, що відповідь @ MichałRudnicki говорить про це найкраще. Не сприймайте мене неправильно, щоразу, коли я задаю ( собі ) питання, і відповідь така eval(): я б як тільки припустив, що поставив запитання неправильно; рідко це "правильна" відповідь, але загальновизнаний варіант - це завжди зло просто неправильно.
Dan Lugg,

якщо я хочу зберегти код у базі даних? як я можу це виконати?
Костянтин XFlash Стратігенас,

@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-скрипт, і може використовувати для отримання інформації / доступу до вашого сервера. Це може бути досить складно переконатися, що користувацький ввід належним чином очищений, перш ніж передавати його на оцінку. Є й інші проблеми ... деякі з них дискусійні


3

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


2

Ще однією причиною evalє зло, що він не може кешуватися кешами байт-кодів PHP, такими як eAccelertor або ACP.


2

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


2

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

Я передбачаю, що незабаром у вас будуть серйозні проблеми ...

Чесно кажучи, абсолютно непотрібною є така непомірна функція, як eval, в інтерпретованій мові, такій як PHP. Я ніколи не бачив, щоб eval виконував програмні функції, які не могли бути виконані іншими, безпечнішими способами ...

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

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


Аргументи "як би ретельно ви не намагалися захистити свій код щодо функції X, розумні хакери завжди на крок попереду ..." можуть бути застосовані до будь-якої іншої техніки, а не лише до X == eval. А отже, для будь-якої функції X: X - корінь усього eval ... er ... зла, тому краще взагалі відмовитися від програмування (таким чином витягуючи килим з-під цих безглуздих хакерів, все-таки перехитривши їх).
Sz.

"абсолютно непотрібною є така непомірна функція, як eval" -> кожен, хто може використовувати такі слова, як "абсолютно", або новачок у програмуванні, або поганий програміст.
union100

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

1

Раніше я часто використовував eval (), але виявив, що в більшості випадків вам не потрібно використовувати eval, щоб робити трюки. Ну, у вас є call_user_func () і call_user_func_array () в PHP. Досить добре статично і динамічно викликати будь-який метод.

Для виконання статичного виклику побудуйте зворотний виклик як масив ('ім'я_класу', 'ім'я_методу') або навіть як такий простий рядок, як 'ім'я_класу :: ім'я_методу'. Для виконання динамічного виклику використовуйте зворотний виклик у стилі масиву ($ object, 'method').

Єдиним розумним використанням eval () є створення власного компілятора. Я зробив один, але eval все ще є злом, тому що його настільки важко налагодити. Найгірше - фатальна помилка в евальованому коді збиває код, який його викликав. Я використовував розширення Parsekit PECL принаймні для перевірки синтаксису, але все одно ніякої радості - спробуйте посилатися на невідомий клас та збій цілої програми.


0

Окрім проблем безпеки, eval () не може бути скомпільований, оптимізований або кешований операційним кодом, тому він завжди буде повільнішим - набагато повільнішим - ніж звичайний php-код. Таким чином, непрацездатно використовувати eval, хоча це не робить його злим. ( gotoце зло, evalце лише погана практика / смердючий код / ​​негарно)


evalотримує нижчий рейтинг зла, ніж goto? Це протилежний день?
JLRishe

Просто я докоряю розробникам php за те, що вони насправді реалізовані gotoв 5.3.
Арон Седерхольм,

1
Ні, для goto-фобів: є інструменти, а є майстри, які ними користуються (чи ні). Ні разу не інструменти , які роблять професійний вид , як на ідіота, коли робити помилки, а нездатність використовувати відповідні інструменти (належним чином). Але в цьому завжди винні інструменти ...
Sz.

0

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

Для мене найгірше те, що це зменшує ремонтопридатність вашого коду:

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