Kiedy Eval Evil jest w PHP?


84

Przez te wszystkie lata, kiedy rozwijałem się w PHP, zawsze słyszałem, że używanie eval()jest złe.

Biorąc pod uwagę poniższy kod, czy nie miałoby sensu użycie drugiej (i bardziej eleganckiej) opcji? Jeśli nie, dlaczego?

// $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 jest ZAWSZE zły, zawsze istnieje lepszy sposób na pisanie kodu, szczególnie od czasu, gdy PHP wprowadziło funkcje anonimowe. W tym przypadku $result = array(); preg_replace_callback('#^enum\s*\(\s*\'|\'\s*\)\s*$#', function($m) use($result) { $result[] = $m[1]; }, $type);
użyłbym

Cóż, szczerze, myślę, że głównym problemem związanym z php nie jest sam język, ale ludzie, którzy go używają. Trzy poprawne odpowiedzi na to pytanie (thomasrutter's, braincracking's i moje) otrzymały głosy negatywne, a nikt nie miał przeciwko nim racji. Z drugiej strony jedna odpowiedź głosi, że „czasami eval () jest jedynym / właściwym rozwiązaniem” bez przykładu lub wyjaśnienia i otrzymuje na nie najwięcej głosów ...
Francois Bourgeois.

Odpowiedzi:


134

Byłbym ostrożny, nazywając eval () czystym złem. Dynamiczna ocena jest potężnym narzędziem i czasami może uratować życie. Dzięki eval () można obejść wady PHP (patrz poniżej).

Główne problemy z eval () to:

  • Potencjalne niebezpieczne wejście. Przekazanie niezaufanego parametru jest sposobem na niepowodzenie. Często upewnienie się, że parametr (lub jego część) jest w pełni zaufany, nie jest łatwym zadaniem.
  • Podstępność. Użycie eval () sprawia, że ​​kod jest sprytny, a przez to trudniejszy do śledzenia. Cytując Briana Kernighana: „ Debugowanie jest dwa razy trudniejsze niż pisanie kodu. Dlatego jeśli piszesz kod tak sprytnie, jak to tylko możliwe, z definicji nie jesteś wystarczająco inteligentny, aby go debugować

Główny problem z faktycznym użyciem eval () jest tylko jeden:

  • Niedoświadczeni programiści, którzy używają go bez odpowiedniego namysłu.

Z reguły postępuję zgodnie z następującą zasadą:

  1. Czasami eval () jest jedynym / właściwym rozwiązaniem.
  2. W większości przypadków należy spróbować czegoś innego.
  3. Jeśli nie masz pewności, przejdź do 2.
  4. W przeciwnym razie bądź bardzo, bardzo ostrożny.

4
Można to zmienić, aby uniknąć również eval (), zwłaszcza jeśli $ className jest na białej liście, co musi być, aby móc korzystać z eval (). Dobra wiadomość jest taka, że ​​od 5.3 $ foo :: bar () działa.
rojoca

@rojoca: Czy możesz nam podać przykład, jak to zrobić bez eval () w PHP 5.2?
Michał Rudnicki

30
Nie jestem pewien, czy liczy się jako eval, czy nie, ale $ result = call_user_func (array ('Foo', 'bar')); działa jak marzenie.
Ionuț G. Stan

3
Dobra uwaga na temat podstępów - w Twoim (prostym) przykładzie zmienna pojawia się „znikąd”. Jeśli kod stanie się trochę bardziej złożony, powodzenia dla następnej osoby, która patrzy na kod i próbuje ścigać tę zmienną (byłem tam, zrobiłem to, dostałem tylko ból głowy i tę kiepską koszulkę).
Piskvor opuścił budynek


40

eval jest zły, gdy istnieje tylko najmniejsza możliwość, że dane wejściowe użytkownika są uwzględnione w ocenianym ciągu. Kiedy przeprowadzasz ewaluację bez treści pochodzących od użytkownika, powinieneś być bezpieczny.

Niemniej jednak powinieneś pomyśleć co najmniej dwa razy przed użyciem eval, wygląda to zwodniczo prosto, ale biorąc pod uwagę obsługę błędów (patrz komentarz VBAssassins), możliwość debugowania itp., Nie jest już takie proste.

A więc praktyczna zasada: zapomnij o tym. Kiedy eval jest odpowiedzią, prawdopodobnie zadajesz niewłaściwe pytanie! ;-)


6
Czasami eval JEST odpowiedzią. Pracujemy nad aplikacją do gier online i bardzo trudno jest tam uniknąć ewaluacji ze względu na bardzo złożone relacje między podmiotami ... ale IMHO w 90% przypadków eval nie jest odpowiedzią.
Jet

19
Byłbym naprawdę ciekawy sytuacji, w której odpowiedzią jest TYLKO eval.
Ionuț G. Stan

2
@Ionut G. Stan Przechowywane w bazie danych niestandardowe wyzwalacze dla obiektów / encji?
Kuroki Kaze

8
Naprawdę nie kupuję, że którekolwiek z nich są uzasadnionymi zastosowaniami eval (). W każdym przypadku zrobienie tego samego bez użycia eval () jest przynajmniej nadal możliwe. To nie tak, że PHP jest okaleczone bez funkcji eval (). To prawda, eval () jest skrótem w takich przypadkach, ale nadal sprawia, że ​​ścieżka kodu jest trochę trudniejsza do naśladowania, a problemy trudniejsze do debugowania. To jest moja opinia.
thomasrutter

4
@Christian: TY powinieneś najpierw napisać przykład (użyj pastebin.com i wklej link tutaj), gdzie Twoim zdaniem uniknięcie użycia eval()jest niemożliwe, jest to lepsze podejście. Wiele osób twierdzi, że eval()w niektórych przypadkach jest to nieuniknione, ale nie podają żadnych konkretnych przykładów, które moglibyśmy argumentować - w ten sposób ta debata nie ma sensu. Więc proszę, ludzie, którzy mówią, że eval()jest to nieuniknione, najpierw udowodnijcie to!
Sk8erPeter

18

eval () jest zawsze równie zła.

„Kiedy eval () nie jest zły?” jest moim zdaniem niewłaściwym pytaniem, ponieważ wydaje się sugerować, że wady używania eval () magicznie znikają w niektórych kontekstach.

Używanie eval () jest ogólnie złym pomysłem, ponieważ zmniejsza czytelność kodu, możliwość przewidywania ścieżki kodu (i możliwych konsekwencji dla bezpieczeństwa) przed uruchomieniem, a tym samym możliwość debugowania kodu. Użycie eval () może również zapobiec optymalizacji kodu ocenianego i otaczającego go kodu przez pamięć podręczną kodu operacji, taką jak Zend Opcache zintegrowana z PHP 5.5 i nowszymi wersjami, lub przez kompilator JIT, taki jak ten w HHVM.

Ponadto nie ma sytuacji, w której użycie eval () byłoby absolutnie konieczne - PHP jest w pełni funkcjonalnym językiem programowania bez niego.

To, czy faktycznie postrzegasz je jako zło, czy też możesz osobiście uzasadnić użycie eval () w niektórych przypadkach, zależy od Ciebie. Dla niektórych zło jest zbyt wielkie, aby kiedykolwiek je usprawiedliwić, a dla innych eval () to przydatny skrót.

Jeśli jednak postrzegasz eval () jako zło, jest ono złe przez cały czas. W zależności od kontekstu nie traci magicznie swojego zła.


1
Robisz piekło z programisty.
Christian,

1
„Ponadto nie ma sytuacji, w której użycie eval () jest absolutnie konieczne” - A co jeśli chcę wykonać kod przechowywany w bazie danych, zgodnie z przykładem podanym w dokumentacji PHP?
Yarin

4
Do tego powiedziałbym, że przechowywanie kodu PHP w bazie danych jest równie złe i równie niepotrzebne. Cokolwiek chcesz osiągnąć, istnieją inne sposoby na zrobienie tego niż przechowywanie PHP w bazie danych lub używanie eval (). Zrób to, jeśli chcesz i jeśli jest to dla ciebie pomocny skrót, ale jeśli traktujesz eval () jako zło, prawdopodobnie również będziesz musiał traktować przechowywanie PHP w bazie danych jako zło.
thomasrutter

12
Dodaje kolejny wektor ataku - iniekcja SQL mogłaby następnie uruchomić dowolny kod PHP na serwerze WWW. Wymaga użycia eval (). Nie można go zoptymalizować za pomocą pamięci podręcznych kodu bajtowego. Narusza zasadę oddzielania kodu i danych. Może to sprawić, że Twoja aplikacja będzie mniej przenośna - aby zaktualizować / naprawić PHP, musisz również zaktualizować bazę danych.
thomasrutter

3
Powiedziałbym, że jeśli coś można osiągnąć tylko za pomocą eval, dobrze byłoby przemyśleć zrobienie tego. Dynamiczne tworzenie klas w czasie wykonywania wydaje się złym pomysłem. Dlaczego nie zastosować generowania kodu i nie wygenerować kodu, który definiuje je z wyprzedzeniem?
thomasrutter

15

W tym przypadku eval jest prawdopodobnie wystarczająco bezpieczny, o ile użytkownik nigdy nie może utworzyć dowolnych kolumn w tabeli w tabeli.

Jednak nie jest już bardziej elegancki. Jest to w zasadzie problem z analizą tekstu, a wykorzystywanie parsera PHP do obsługi wydaje się trochę hakerskie. Jeśli chcesz nadużywać funkcji językowych, dlaczego nie nadużywać parsera JSON? Przynajmniej w przypadku parsera JSON nie ma żadnej możliwości wstrzyknięcia kodu.

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

Wyrażenie regularne jest prawdopodobnie najbardziej oczywistym sposobem. Możesz użyć jednego wyrażenia regularnego, aby wyodrębnić wszystkie wartości z tego ciągu:

$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
mimo że to nie odpowiada na pytanie samo w sobie, jest to bardzo dobra odpowiedź na pytanie: jaki byłby najlepszy sposób przeanalizowania wyliczenia ()… dziękuję;)
Pierre Spring

13

eval() jest powolny, ale nie nazwałbym tego złem.

To zły użytek, jaki z niego zrobimy, może prowadzić do wstrzyknięcia kodu i być złym.

Prosty przykład:

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

Szkodliwy przykład:

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

Radziłbym ci nie używać, eval()ale jeśli to zrobisz, upewnij się, że potwierdziłeś / dodałeś do białej listy wszystkie dane wejściowe.


12

Gdy używasz obcych danych (takich jak dane wejściowe użytkownika) wewnątrz eval.

W powyższym przykładzie nie stanowi to problemu.


7

rażąco ukradnę zawartość tutaj:

  1. Eval ze swej natury zawsze będzie problemem bezpieczeństwa.

  2. Poza obawami dotyczącymi bezpieczeństwa, eval ma również problem z niewiarygodnie powolnym działaniem. Podczas moich testów na PHP 4.3.10 jego 10 razy wolniej niż normalny kod i 28 razy wolniej na PHP 5.1 beta1.

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


5

eval()jest zawsze zły.

  • ze względów bezpieczeństwa
  • ze względu na wydajność
  • ze względu na czytelność / możliwość ponownego wykorzystania
  • ze względów IDE / narzędzi
  • z powodów debugowania
  • zawsze jest lepszy sposób

@bracketworks: Ale mylisz się - wszystkie te nieodłączne problemy nie znikają w magiczny sposób w niektórych sytuacjach. Dlaczego mieliby to robić? Weźmy na przykład wydajność: czy naprawdę myślisz, że interpreter wykona niezwykle wolną funkcję eval () ze zwiększoną szybkością tylko dlatego, że użyłeś jej w jakiś mistyczny, „niezły” sposób? Albo że debugowanie krok po kroku zadziała w twojej klauzuli ewaluacyjnej pewnego szczęśliwego dnia?
Francois Bourgeois

3
Szczerze mówiąc, myślę, że odpowiedź @Michała Rudnickiego mówi najlepiej. Nie zrozum mnie źle, ilekroć zadaję sobie pytanie, a odpowiedź brzmi: eval()tak szybko, jak zakładam, że zadałem pytanie źle; rzadko jest to „właściwa” odpowiedź, ale stwierdzenie, że jest ono zawsze złe, jest po prostu błędne.
Dan Lugg

czy chcę przechowywać kod w bazie danych? jak mogę to wykonać?
Konstantin XFlash Stratigenas

@KonstantinXFlashStratigenas Powinieneś zadać sobie pytanie, dlaczego przechowujesz kod wykonywalny w bazie danych. Baza danych jest przeznaczona dla danych wynikających z Twojego kodu - nie dla Twojego kodu. Przynajmniej zwiększasz wektory ataku.
Jack B

4

Zwróciłbym również uwagę na osoby obsługujące Twój kod.

eval () nie jest łatwe do obejrzenia i dowiedzenia się, co powinno się wydarzyć, twój przykład nie jest taki zły, ale w innych miejscach może to być właściwy koszmar.


4

Osobiście uważam, że ten kod jest nadal dość zły, ponieważ nie komentujesz tego, co robi. Nie testuje również swoich danych wejściowych pod kątem ważności, co czyni go bardzo delikatnym.

Uważam również, że ponieważ 95% (lub więcej) zastosowań eval jest aktywnie niebezpiecznych, niewielka potencjalna oszczędność czasu, jaką może on zapewnić w innych przypadkach, nie jest warta oddawania się złej praktyce jego używania. Poza tym będziesz musiał później wyjaśnić swoim sługom, dlaczego twoje użycie eval jest dobre, a ich złe.

I, oczywiście, twój PHP wygląda jak Perl;)

Istnieją dwa kluczowe problemy z eval () (jako scenariusz „ataku iniekcyjnego”):

1) Może spowodować obrażenia 2) Może po prostu się zawiesić

i bardziej społeczny niż techniczny:

3) Będzie to kusić ludzi do niewłaściwego używania go jako skrótu w innym miejscu

W pierwszym przypadku ryzykujesz (oczywiście nie wtedy, gdy oceniasz znany ciąg znaków) wykonania dowolnego kodu. Twoje dane wejściowe mogą jednak nie być tak znane lub ustalone, jak myślisz.

Bardziej prawdopodobne jest (w tym przypadku) awarię, a ciąg znaków zakończy się nieuzasadnionym niejasnym komunikatem o błędzie. IMHO, cały kod powinien zawieść tak starannie, jak to możliwe, aw przypadku niepowodzenia powinien zgłosić wyjątek (jako najłatwiejszą do obsługi formę błędu).

Sugerowałbym, że w tym przykładzie kodujesz przez przypadek, a nie na zachowanie. Tak, instrukcja wyliczenia SQL (i czy na pewno wyliczenie tego pola? - czy wywołałeś właściwe pole odpowiedniej tabeli odpowiedniej wersji bazy danych? Czy faktycznie odpowiedziała?) Wygląda jak składnia deklaracji tablicy w PHP, ale sugerowałbym, że naprawdę nie chcesz znaleźć najkrótszej ścieżki od wejścia do wyjścia, ale raczej zająć się określonym zadaniem:

  • Zidentyfikuj, że masz wyliczenie
  • Wyodrębnij listę wewnętrzną
  • Rozpakuj wartości listy

To mniej więcej to, co robi twoja opcja, ale zawinąłbym wokół niej kilka „if” i komentarzy dla jasności i bezpieczeństwa (np. Jeśli pierwszy mecz nie pasuje, wyrzuć wyjątek lub ustaw wynik zerowy).

Nadal istnieją pewne możliwe problemy z przecinkami lub cudzysłowami i prawdopodobnie powinieneś rozpakować dane, a następnie usunąć je z cudzysłowu, ale przynajmniej traktuje dane jako dane, a nie jako kod.

W wersji preg_version najgorszym wynikiem będzie prawdopodobnie $ result = null, aw wersji eval najgorszy jest nieznany, ale przynajmniej awaria.


3

eval ocenia łańcuch jako kod, problem polega na tym, że jeśli łańcuch jest w jakikolwiek sposób „skażony”, może to narazić na ogromne zagrożenie bezpieczeństwa. Zwykle problem występuje w przypadku, gdy dane wejściowe użytkownika są oceniane w ciągu znaków, w wielu przypadkach użytkownik mógłby wprowadzić kod (na przykład php lub ssi), który jest następnie uruchamiany w ramach eval, działałby z takimi samymi uprawnieniami jak skrypt php i mógłby być wykorzystywane do uzyskiwania informacji / dostępu do Twojego serwera. Upewnienie się, że dane wejściowe użytkownika są odpowiednio wyczyszczone przed przekazaniem ich do eval, może być dość trudne. Są inne problemy ... niektóre z nich są dyskusyjne


3

PHP radzi, aby napisać kod w taki sposób, aby mógł być wykonywany przez call_user_func zamiast robić jawne ewaluacje.


2

Innym powodem evaljest to, że nie może być buforowany przez bufory kodu bajtowego PHP, takie jak eAccelertor lub ACP.


2

To złe programowanie sprawia, że ​​eval () jest złym, a nie funkcja. Czasami go używam, ponieważ nie mogę go obejść w programowaniu dynamicznym w wielu witrynach. Nie mogę przeprowadzić parsowania PHP w jednej witrynie, ponieważ nie otrzymam tego, co chcę. Po prostu otrzymam wynik! Cieszę się, że istnieje funkcja eval (), która znacznie ułatwia mi życie. Wprowadzane przez użytkownika? Hakerzy zaczepiają tylko źli programiści. O to się nie martwię.


2

To złe programowanie sprawia, że ​​eval () jest złym, a nie funkcja. Czasami go używam, ponieważ nie mogę go obejść w programowaniu dynamicznym w wielu witrynach. Nie mogę przeprowadzić parsowania PHP w jednej witrynie, ponieważ nie otrzymam tego, co chcę. Po prostu otrzymam wynik! Cieszę się, że istnieje funkcja eval (), która znacznie ułatwia mi życie. Wprowadzane przez użytkownika? Hakerzy zaczepiają tylko źli programiści. O to się nie martwię.

Przewiduję, że wkrótce będziesz miał poważne problemy ...

Szczerze mówiąc, nie ma absolutnie żadnego pożytku z wygórowanej funkcji, takiej jak eval, w języku interpretowanym, takim jak PHP. Nigdy nie widziałem, aby eval wykonywał funkcje programu, których nie można byłoby wykonać innymi, bezpieczniejszymi sposobami ...

Z całego serca zgadzam się, że Eval jest źródłem wszelkiego zła dla wszystkich ludzi, którzy uważają, że pomocne będzie testowanie danych wejściowych użytkownika. Pomyśl dwa razy, dane wejściowe użytkownika mogą przybierać wiele różnych form, a hakerzy wykorzystują tę funkcję, na której Ci nie zależy. Moim zdaniem po prostu unikaj eval.

Widziałem przygotowane przykłady nadużywania funkcji eval, która przewyższała moją własną kreatywność. Z punktu widzenia bezpieczeństwa unikaj za wszelką cenę, a nawet posunąłbym się nawet do żądania, aby było to przynajmniej opcją w konfiguracji PHP, a nie „daną”.


Rozumowanie „bez względu na to, jak ostrożnie próbujesz zabezpieczyć swój kod pod kątem funkcji X, sprytni hakerzy są zawsze o krok do przodu…” można zastosować do każdej innej techniki, nie tylko X == eval. I dlatego dla każdej funkcji X: X jest źródłem wszelkiego eval ... eee ... zła, więc lepiej całkowicie zrezygnować z programowania (wyciągając dywan spod tych głupich hakerów, wciąż ich przechytrzając).
Sz.

„nie ma absolutnie żadnego pożytku z wygórowanych funkcji, takich jak eval” -> każdy, kto potrafi używać słów takich jak „absolutnie”, jest albo nowy w programowaniu, albo kiepskim programistą.
jedność100

2

Oto rozwiązanie do uruchamiania kodu PHP pobranego z bazy danych bez użycia eval. Zezwala na wszystkie funkcje w zakresie i wyjątki:

$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');

Zasadniczo tworzy unikalną funkcję z zawartym kodem w pliku tekstowym, dołącza plik, wywołuje funkcję, a po zakończeniu usuwa plik. Używam tego do codziennego pozyskiwania / synchronizacji bazy danych, gdzie każdy krok wymaga unikalnego kodu do obsługi. To rozwiązało wszystkie problemy, z którymi miałem do czynienia.


To wygląda na odpowiedź na odpowiedź; opublikuj go jako taki, kiedy możesz.
rfornal

1

Kiedyś często używałem eval (), ale znalazłem większość przypadków, w których nie trzeba używać eval do robienia sztuczek. Cóż, masz call_user_func () i call_user_func_array () w PHP. Wystarczy statycznie i dynamicznie wywołać dowolną metodę.

Aby wykonać statyczne wywołanie, skonstruuj swoje wywołanie zwrotne jako tablicę („nazwa_klasy”, „nazwa_metody”) lub nawet jako prosty ciąg znaków, taki jak „nazwa_klasy :: nazwa_metody”. Aby wykonać wywołanie dynamiczne, użyj wywołania zwrotnego w stylu tablicy ($ object, 'method').

Jedynym sensownym zastosowaniem eval () jest napisanie własnego kompilatora. Zrobiłem jeden, ale eval jest nadal zły, ponieważ jest tak cholernie trudny do debugowania. Najgorsze jest to, że błąd krytyczny w ewaluowanym kodzie powoduje awarię kodu, który go wywołał. Użyłem rozszerzenia Parsekit PECL, aby przynajmniej sprawdzić składnię, ale nadal bez radości - spróbuj odwołać się do nieznanych klas i awarii całej aplikacji.


0

Poza względami bezpieczeństwa, eval () nie może zostać skompilowana, zoptymalizowana ani zbuforowana w kodzie operacyjnym, więc zawsze będzie wolniejsza - o wiele wolniejsza - niż normalny kod php. Dlatego używanie eval jest niewykonalne, chociaż to nie czyni go złym. ( gotojest zły, evalto tylko zła praktyka / śmierdzący kod / brzydki)


evaldostaje niższą ocenę zła niż goto? Czy to przeciwny dzień?
JLRishe,

Chodzi o to, że mam pretensje do twórców php za faktyczne wdrożenie gotow 5.3.
Aron Cederholm

1
Nie, dla goto-fobianów: są narzędzia i są rzemieślnicy, którzy ich używają (lub nie). To nigdy nie narzędzia sprawiają, że profesjonalista przy popełnianiu błędów wygląda jak idiota, ale raczej niemożność użycia odpowiednich narzędzi (właściwie). Ale to zawsze narzędzia winne ...
Sz.

0

Większość ludzi zwróci uwagę na fakt, że może to być niebezpieczne, gdy masz do czynienia z danymi wejściowymi użytkownika (z którymi można sobie poradzić).

Dla mnie najgorsze jest to, że ogranicza to łatwość utrzymania twojego kodu:

  • Trudne do debugowania
  • Trudno zaktualizować
  • Ogranicza użycie narzędzi i pomocników (takich jak IDE)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.