Funkcja eval jest potężnym i łatwym sposobem na dynamiczne generowanie kodu, więc jakie są zastrzeżenia?
Funkcja eval jest potężnym i łatwym sposobem na dynamiczne generowanie kodu, więc jakie są zastrzeżenia?
Odpowiedzi:
Niewłaściwe użycie eval otwiera kod do ataków z zastrzykiem
Debugowanie może być trudniejsze (brak numerów linii itp.)
kod eval'd działa wolniej (brak możliwości kompilacji / buforowania kodu eval'd)
Edycja: Jak zauważył @Jeff Walden w komentarzach, # 3 jest dziś mniej prawdziwe niż w 2008 roku. Jednakże, chociaż może się zdarzyć buforowanie skompilowanych skryptów, będzie to ograniczone tylko do skryptów, które są powtarzane bez modyfikacji. Bardziej prawdopodobnym scenariuszem jest ewaluacja skryptów, które za każdym razem ulegały niewielkiej modyfikacji i jako takie nie mogły być buforowane. Powiedzmy, że NIEKTÓRY sprawdzony kod działa wolniej.
badHackerGuy'); doMaliciousThings();
a jeśli weźmiesz moją nazwę użytkownika, przekształcisz ją w jakiś skrypt i sprawdzę w przeglądarkach innych osób, będę mógł uruchomić dowolny skrypt javascript na swoich komputerach (np. Zmusić ich do +1 moich postów, opublikować swoje dane na moim serwerze itp.)
debugger;
instrukcję do kodu źródłowego. Spowoduje to zatrzymanie wykonywania programu na tej linii. Następnie możesz dodać punkty przerwania debugowania, jakby to był tylko kolejny plik JS.
eval nie zawsze jest zły. Są chwile, kiedy jest to całkowicie właściwe.
Jednak eval jest obecnie i historycznie nadmiernie wykorzystywany przez ludzi, którzy nie wiedzą, co robią. Obejmuje to niestety osoby piszące samouczki JavaScript, aw niektórych przypadkach może to mieć konsekwencje dla bezpieczeństwa - lub częściej proste błędy. Im więcej możemy zrobić, aby przerzucić znak zapytania nad eval, tym lepiej. Za każdym razem, gdy używasz eval, musisz sprawdzać rozsądek, co robisz, ponieważ są szanse, że możesz to zrobić w lepszy, bezpieczniejszy i czystszy sposób.
Aby podać zbyt typowy przykład, aby ustawić kolor elementu o identyfikatorze przechowywanym w zmiennej „potato”:
eval('document.' + potato + '.style.color = "red"');
Gdyby autorzy powyższego kodu mieli pojęcie o podstawach działania obiektów JavaScript, zdaliby sobie sprawę, że zamiast literalnych nazw kropek można użyć nawiasów kwadratowych, co eliminuje potrzebę ewaluacji:
document[potato].style.color = 'red';
... który jest znacznie łatwiejszy do odczytania, a także mniej potencjalnie błędny.
(Ale wtedy ktoś, kto / naprawdę / wiedział, co robią, powiedziałby:
document.getElementById(potato).style.color = 'red';
który jest bardziej niezawodny niż stara sztuczka polegająca na uzyskiwaniu dostępu do elementów DOM bezpośrednio z obiektu dokumentu).
JSON.parse()
zamiast eval()
JSON?
Uważam, że dzieje się tak dlatego, że może on wykonać dowolną funkcję JavaScript z ciągu znaków. Korzystanie z niego ułatwia ludziom wstrzykiwanie fałszywego kodu do aplikacji.
Przychodzą mi na myśl dwa punkty:
Bezpieczeństwo (ale dopóki sam generujesz ciąg do oceny, może to nie być problemem)
Wydajność: dopóki kod do wykonania nie będzie znany, nie można go zoptymalizować. (o javascript i wydajności, z pewnością prezentacja Steve'a Yegge )
eval
a następnie ten mały fragment kodu działa w przeglądarce użytkownika B
Przekazywanie danych wejściowych użytkownika do eval () stanowi zagrożenie bezpieczeństwa, ale każde wywołanie eval () tworzy nową instancję interpretera JavaScript. Może to być świnia zasobów.
Głównie trudniej jest utrzymać i debugować. To jest jak goto
. Możesz z niego korzystać, ale trudniej jest znaleźć problemy i trudniej ludziom, którzy mogą później wprowadzić zmiany.
Należy pamiętać, że często można użyć eval () do wykonania kodu w innym ograniczonym środowisku - witryny sieci społecznościowych, które blokują określone funkcje JavaScript, mogą czasem zostać oszukane przez rozbicie ich na blok eval -
eval('al' + 'er' + 't(\'' + 'hi there!' + '\')');
Więc jeśli chcesz uruchomić kod JavaScript, który w innym przypadku może nie być dozwolony ( Myspace , patrzę na ciebie ...), eval () może być użyteczną sztuczką.
Jednak z wszystkich wyżej wymienionych powodów nie powinieneś używać go do własnego kodu, w którym masz pełną kontrolę - to po prostu nie jest konieczne, a lepiej umieścić na półce „trudnych hackerów JavaScript”.
[]["con"+"struc"+"tor"]["con"+"struc"+"tor"]('al' + 'er' + 't(\'' + 'hi there!' + '\')')()
O ile nie pozwalasz eval () na dynamiczną treść (przez cgi lub wprowadzanie danych), jest ona tak bezpieczna i solidna jak wszystkie inne JavaScript na twojej stronie.
Wraz z resztą odpowiedzi nie sądzę, aby stwierdzenia eval mogły mieć zaawansowaną minimalizację.
Jest to możliwe zagrożenie bezpieczeństwa, ma inny zakres wykonywania i jest dość nieefektywne, ponieważ tworzy całkowicie nowe środowisko skryptowe do wykonywania kodu. Zobacz tutaj, aby uzyskać więcej informacji: eval .
Jest to jednak dość przydatne i używane z umiarem może dodać wiele dobrej funkcjonalności.
O ile nie masz 100% pewności, że oceniany kod pochodzi z zaufanego źródła (zwykle z własnej aplikacji), to niezawodny sposób na narażenie twojego systemu na atak skryptu między witrynami.
Wiem, że ta dyskusja jest stara, ale bardzo podoba mi się to podejście Google i chciałem podzielić się tym z innymi;)
Drugą rzeczą jest to, że im lepiej, tym więcej próbujesz zrozumieć, a na koniec po prostu nie wierzysz, że coś jest dobre lub złe tylko dlatego, że ktoś tak powiedział :) To bardzo inspirujące wideo, które pomogło mi więcej myśleć :) DOBRE PRAKTYKI są dobre, ale nie używaj ich bezmyślnie :)
Niekoniecznie tak źle, pod warunkiem, że wiesz, w jakim kontekście go używasz.
Jeśli twoja aplikacja używa eval()
do utworzenia obiektu z jakiegoś JSON, który wrócił z XMLHttpRequest do twojej własnej strony, utworzonej przez zaufany kod po stronie serwera, prawdopodobnie nie jest to problem.
Niezaufany kod JavaScript po stronie klienta i tak nie może wiele zdziałać. Pod warunkiem, że to, co wykonujesz eval()
, pochodzi z rozsądnego źródła, nic ci nie jest.
To znacznie obniża poziom pewności co do bezpieczeństwa.
Jeśli chcesz, aby użytkownik wprowadził niektóre funkcje logiczne i ocenił AND AND, wówczas funkcja eval JavaScript jest idealna. Mogę zaakceptować dwa ciągi eval(uate) string1 === string2
itd.
Jeśli zauważysz użycie eval () w swoim kodzie, pamiętaj mantrę „eval () jest zły”.
Ta funkcja pobiera dowolny ciąg znaków i wykonuje go jako kod JavaScript. Gdy dany kod jest wcześniej znany (nie jest określany w czasie wykonywania), nie ma powodu, aby używać eval (). Jeśli kod jest generowany dynamicznie w czasie wykonywania, często istnieje lepszy sposób na osiągnięcie celu bez eval (). Na przykład użycie notacji w nawiasach kwadratowych w celu uzyskania dostępu do właściwości dynamicznych jest lepsze i prostsze:
// antipattern
var property = "name";
alert(eval("obj." + property));
// preferred
var property = "name";
alert(obj[property]);
Używanie eval()
ma również wpływ na bezpieczeństwo, ponieważ możesz wykonywać kod (na przykład pochodzący z sieci), który został zmodyfikowany. Jest to powszechny antypattern w przypadku odpowiedzi JSON z żądania Ajax. W takich przypadkach lepiej jest użyć wbudowanych metod przeglądarki do przeanalizowania odpowiedzi JSON, aby upewnić się, że jest ona bezpieczna i poprawna. Dla przeglądarek, które nie obsługująJSON.parse()
natywnie, możesz użyć biblioteki z JSON.org.
Ważne jest również, aby pamiętać, że przekazywanie ciągów do setInterval()
, setTimeout()
a Function()
konstruktor jest w większości podobny do używania eval()
i dlatego należy go unikać.
Za kulisami JavaScript wciąż musi oceniać i wykonywać ciąg przekazywany jako kod programowania:
// antipatterns
setTimeout("myFunc()", 1000);
setTimeout("myFunc(1, 2, 3)", 1000);
// preferred
setTimeout(myFunc, 1000);
setTimeout(function () {
myFunc(1, 2, 3);
}, 1000);
Korzystanie z nowego konstruktora Function () jest podobne do eval () i należy do niego podchodzić ostrożnie. Może to być potężny konstrukt, ale często jest niewłaściwie wykorzystywany. Jeśli absolutnie musisz użyćeval()
, możesz zamiast tego rozważyć użycie nowej funkcji ().
Istnieje niewielka potencjalna korzyść, ponieważ kod oceniany w nowej funkcji () będzie działał w zasięgu funkcji lokalnej, więc wszelkie zmienne zdefiniowane przy pomocy var w analizowanym kodzie nie staną się globalne automatycznie.
Innym sposobem zapobiegania automatycznym globalizacjom jest zawinięcie
eval()
połączenia w natychmiastową funkcję.
Oprócz możliwych problemów z bezpieczeństwem podczas wykonywania kodu przesłanego przez użytkownika, przez większość czasu istnieje lepszy sposób, który nie wymaga ponownej analizy kodu za każdym razem, gdy jest wykonywany. Anonimowe funkcje lub właściwości obiektu mogą zastąpić większość zastosowań eval i są znacznie bezpieczniejsze i szybsze.
To jeden z dobrych artykułów mówiących o ewaluacji i tym, jak nie jest to zło: http://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/
Nie twierdzę, że powinieneś zabraknąć i zacząć używać eval () wszędzie. W rzeczywistości jest bardzo niewiele dobrych przypadków użycia eval (). Zdecydowanie istnieją obawy dotyczące przejrzystości kodu, możliwości debugowania, a na pewno wydajności, których nie należy przeoczyć. Ale nie powinieneś bać się go używać, gdy masz przypadek, w którym eval () ma sens. Spróbuj go najpierw nie używać, ale nie pozwól, aby ktokolwiek przestraszył cię, że twój kod jest bardziej kruchy lub mniej bezpieczny, gdy eval () jest właściwie używany.
eval () jest bardzo potężny i może być użyty do wykonania instrukcji JS lub oceny wyrażenia. Ale pytanie nie dotyczy użycia eval (), ale powiedzmy tylko, w jaki sposób złośliwy podmiot wpływa na ciąg znaków uruchamiany za pomocą eval (). Na koniec będziesz uruchamiać złośliwy kod. Z mocą wiąże się wielka odpowiedzialność. Więc używaj go mądrze, jeśli go używasz. Nie ma to większego związku z funkcją eval (), ale ten artykuł zawiera całkiem dobre informacje: http://blogs.popart.com/2009/07/javascript-injection-attacks/ Jeśli szukasz podstaw eval () spójrz tutaj: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
Silnik JavaScript ma wiele optymalizacji wydajności, które wykonuje podczas fazy kompilacji. Niektóre z nich sprowadzają się do tego, że są w stanie statystycznie analizować kod podczas leksykacji i wstępnie ustalić, gdzie znajdują się wszystkie deklaracje zmiennych i funkcji, tak że potrzeba mniej wysiłku, aby rozpoznać identyfikatory podczas wykonywania.
Ale jeśli Engine znajdzie eval (..) w kodzie, zasadniczo musi założyć, że cała jego świadomość lokalizacji identyfikatora może być nieprawidłowa, ponieważ nie może wiedzieć w czasie leksykalizacji, jaki kod możesz przekazać eval (..) aby zmodyfikować zakres leksykalny lub zawartość obiektu, który można przekazać, aby utworzyć nowy zakres leksykalny, z którym należy się zapoznać.
Innymi słowy, w sensie pesymistycznym większość dokonanych przez niego optymalizacji jest bezcelowa, jeśli eval (..) jest obecny, więc po prostu wcale nie wykonuje optymalizacji.
To wszystko wyjaśnia.
Odniesienie :
https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20&%20closures/ch2.md#eval
https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20&%20closures/ch2.md#performance
Nie zawsze jest to zły pomysł. Weźmy na przykład generowanie kodu. Niedawno napisałem bibliotekę o nazwie Hyperbars, która wypełnia lukę między domem wirtualnym a kierownicą . Robi to, analizując szablon kierownicy i konwertując go do hiperskryptu, który jest następnie używany przez virtual-dom. Indeks górny jest najpierw generowany jako ciąg, a przed zwróceniem eval()
przekształca go w kod wykonywalny. eval()
W tej szczególnej sytuacji znalazłem dokładne przeciwieństwo zła.
Zasadniczo od
<div>
{{#each names}}
<span>{{this}}</span>
{{/each}}
</div>
Do tego
(function (state) {
var Runtime = Hyperbars.Runtime;
var context = state;
return h('div', {}, [Runtime.each(context['names'], context, function (context, parent, options) {
return [h('span', {}, [options['@index'], context])]
})])
}.bind({}))
Wydajność eval()
nie jest problemem w takiej sytuacji, ponieważ wystarczy zinterpretować wygenerowany ciąg tylko raz, a następnie wielokrotnie użyć wyjściowego pliku wykonywalnego.
Można zobaczyć, w jaki sposób generowania kodu został osiągnięty, jeśli jesteś ciekawy tutaj .
Chciałbym powiedzieć, że nie ma znaczenia, czy używasz eval()
javascript, który jest uruchamiany w przeglądarkach. * (Zastrzeżenie)
Wszystkie współczesne przeglądarki mają konsolę programisty, w której można mimo to wykonać dowolny skrypt javascript, a każdy pół-inteligentny programista może spojrzeć na twoje źródło JS i umieścić dowolne jego części w konsoli programistów, aby zrobić to, co zechce.
* Tak długo, jak punkty końcowe serwera mają poprawną weryfikację i dezynfekcję wartości dostarczonych przez użytkownika, nie powinno mieć znaczenia, co zostanie przeanalizowane i sprawdzone w javascript po stronie klienta.
Jeśli jednak zapytasz, czy jest odpowiedni do użycia eval()
w PHP, odpowiedź brzmi NIE , chyba że umieścisz na białej liście wartości, które mogą zostać przekazane do twojego polecenia eval.
Nie będę próbował obalić niczego, co powiedziałem do tej pory, ale zaoferuję użycie eval (), którego (o ile wiem) nie da się zrobić w żaden inny sposób. Prawdopodobnie istnieją inne sposoby na zakodowanie tego i prawdopodobnie sposoby na jego optymalizację, ale odbywa się to ręcznie i bez żadnych dzwonków i gwizdków dla jasności, aby zilustrować użycie eval, które tak naprawdę nie ma żadnych innych alternatyw. To znaczy: dynamiczne (a dokładniej) programowo tworzone nazwy obiektów (w przeciwieństwie do wartości).
//Place this in a common/global JS lib:
var NS = function(namespace){
var namespaceParts = String(namespace).split(".");
var namespaceToTest = "";
for(var i = 0; i < namespaceParts.length; i++){
if(i === 0){
namespaceToTest = namespaceParts[i];
}
else{
namespaceToTest = namespaceToTest + "." + namespaceParts[i];
}
if(eval('typeof ' + namespaceToTest) === "undefined"){
eval(namespaceToTest + ' = {}');
}
}
return eval(namespace);
}
//Then, use this in your class definition libs:
NS('Root.Namespace').Class = function(settings){
//Class constructor code here
}
//some generic method:
Root.Namespace.Class.prototype.Method = function(args){
//Code goes here
//this.MyOtherMethod("foo")); // => "foo"
return true;
}
//Then, in your applications, use this to instantiate an instance of your class:
var anInstanceOfClass = new Root.Namespace.Class(settings);
EDYCJA: nawiasem mówiąc, nie sugerowałbym (ze wszystkich wspomnianych wcześniej względów bezpieczeństwa), aby oprzeć nazwy obiektów na danych wprowadzonych przez użytkownika. Nie mogę sobie wyobrazić żadnego dobrego powodu, dla którego chciałbyś to zrobić. Mimo to pomyślałem, że zwrócę uwagę, że nie byłby to dobry pomysł :)
namespaceToTest[namespaceParts[i]]
, bez potrzeby ewaluacji tutaj, więc if(typeof namespaceToTest[namespaceParts[i]] === 'undefined') { namespaceToTest[namespaceParts[i]] = {};
jedyną różnicą dlaelse namespaceToTest = namespaceToTest[namespaceParts[i]];
Zbieranie śmieci
Odśmiecanie pamięci przeglądarki nie ma pojęcia, czy ewaluowany kod można usunąć z pamięci, więc zachowuje go do momentu ponownego załadowania strony. Nieźle, jeśli użytkownicy będą wkrótce na Twojej stronie, ale może to stanowić problem dla aplikacji internetowych.
Oto skrypt pokazujący problem
https://jsfiddle.net/CynderRnAsh/qux1osnw/
document.getElementById("evalLeak").onclick = (e) => {
for(let x = 0; x < 100; x++) {
eval(x.toString());
}
};
Coś tak prostego jak powyższy kod powoduje przechowywanie niewielkiej ilości pamięci do momentu śmierci aplikacji. Gorzej, gdy ewaluowany skrypt jest gigantyczną funkcją i jest wywoływany w interwale.