Почему использование JavaScript-функции eval - плохая идея?
Функция eval - это мощный и простой способ динамического генерирования кода, так каковы предостережения?
25 ответов
Неправильное использование eval открывает ваш код для инъекционных атак
Отладка может быть более сложной (без номеров строк и т. Д.)
eval'd код выполняется медленнее (нет возможности компилировать / кэшировать eval'd код)
Редактировать: Как @Jeff Walden указывает в комментариях, #3 сегодня менее верен, чем это было в 2008 году. Однако, хотя может происходить некоторое кэширование скомпилированных скриптов, это будет ограничено только сценариями, которые evald повторялся без изменений. Более вероятным сценарием является то, что вы используете сценарии, которые каждый раз подвергались небольшим изменениям и поэтому не могли быть кэшированы. Давайте просто скажем, что НЕКОТОРЫЙ eval'd код выполняется медленнее.
Eval не всегда зло. Есть моменты, когда это совершенно уместно.
Однако eval в настоящее время и исторически массово используется людьми, которые не знают, что делают. К сожалению, это включает людей, пишущих учебники по JavaScript, и в некоторых случаях это может иметь последствия для безопасности - или, чаще, простые ошибки. Таким образом, чем больше мы можем сделать, чтобы поставить вопросительный знак над eval, тем лучше. Каждый раз, когда вы используете eval, вам нужно тщательно проверить, что вы делаете, потому что, скорее всего, вы могли бы сделать это лучше, безопаснее и чище.
Чтобы дать слишком типичный пример, установить цвет элемента с идентификатором, хранящимся в переменной 'potato':
eval('document.' + potato + '.style.color = "red"');
Если бы авторы приведенного выше кода имели представление об основах работы объектов JavaScript, они бы поняли, что вместо литеральных точечных имен можно использовать квадратные скобки, устраняя необходимость в eval:
document[potato].style.color = 'red';
... который намного легче читать, а также менее глючит.
(Но тогда кто-то, кто / действительно / знал, что они делают, скажет:
document.getElementById(potato).style.color = 'red';
что более надежно, чем хитрый старый прием доступа к элементам DOM прямо из объекта документа.)
Я считаю, что это потому, что он может выполнять любую функцию JavaScript из строки. С его помощью людям проще внедрить в приложение мошеннический код.
Как правило, это проблема, только если вы передаете пользовательский ввод eval.
На ум приходят два момента:
Безопасность (но пока вы сами генерируете строку для оценки, это может быть не проблема)
Производительность: до тех пор, пока исполняемый код неизвестен, его нельзя оптимизировать. (о JavaScript и производительности, конечно же, презентации Стива Йегге)
Передача пользовательского ввода в eval() представляет собой угрозу безопасности, но каждый вызов eval() создает новый экземпляр интерпретатора JavaScript. Это может быть ресурсным боровом.
В основном, это намного сложнее поддерживать и отлаживать. Это как goto
, Вы можете использовать его, но это усложняет поиск проблем и усложняет задачу людям, которым, возможно, потребуется внести изменения позже.
Следует иметь в виду, что вы часто можете использовать eval() для выполнения кода в иным образом ограниченной среде - сайты социальных сетей, которые блокируют определенные функции JavaScript, иногда можно обмануть, разбив их на блоки eval -
eval('al' + 'er' + 't(\'' + 'hi there!' + '\')');
Так что, если вы хотите запустить какой-то код JavaScript, который иначе не мог бы быть разрешен ( Myspace, я смотрю на вас...), тогда eval() может быть полезным трюком.
Однако, по всем причинам, указанным выше, вы не должны использовать его для своего собственного кода, где у вас есть полный контроль - это просто не нужно, и лучше отойти на полку "хитрых хаков JavaScript".
Если вы не разрешите eval() динамический контент (через cgi или input), он будет таким же безопасным и надежным, как и любой другой JavaScript на вашей странице.
Наряду с остальными ответами, я не думаю, что у заявлений eval может быть продвинутая минимизация.
Это возможный риск для безопасности, он имеет другую область исполнения и совершенно неэффективен, поскольку создает совершенно новую среду сценариев для выполнения кода. Смотрите здесь для получения дополнительной информации: Eval.
Это довольно полезно, хотя и использование с модерацией может добавить много хорошей функциональности.
Это не обязательно так плохо, если вы знаете, в каком контексте вы его используете.
Если ваше приложение использует eval()
чтобы создать объект из некоторого JSON, который вернулся из XMLHttpRequest на ваш собственный сайт, созданный вашим доверенным серверным кодом, это, вероятно, не проблема.
Ненадежный код JavaScript на стороне клиента не может сделать это так или иначе. При условии, что вы выполняете eval()
Он поступил из разумного источника, вы в порядке.
Если вы не уверены на 100%, что оцениваемый код получен из надежного источника (обычно из вашего собственного приложения), то это верный способ подвергнуть вашу систему атаке межсайтового скриптинга.
Я знаю, что эта дискуссия старая, но мне действительно нравится этот подход от Google, и я хотел поделиться этим чувством с другими;)
Другое дело, что чем лучше Ты получаешь, тем больше Ты пытаешься понять и, в конце концов, Ты просто не веришь, что что-то хорошо или плохо, только потому, что кто-то так сказал:) Это очень вдохновляющее видео, которое помогло мне больше думать самостоятельно:) ХОРОШИЕ ПРАКТИКИ хороши, но не используйте их безрассудно:)
Если вы хотите, чтобы пользователь вводил некоторые логические функции и оценивал И ИЛИ, тогда функция JavaScript eval является идеальной. Я могу принять две строки и eval(uate) string1 === string2
, так далее.
Если вы заметили использование eval() в своем коде, помните мантру "eval() - зло".
Эта функция принимает произвольную строку и выполняет ее как код JavaScript. Когда рассматриваемый код известен заранее (не определяется во время выполнения), нет смысла использовать eval(). Если код генерируется динамически во время выполнения, часто есть лучший способ достичь цели без eval(). Например, просто использовать квадратную скобку для доступа к динамическим свойствам лучше и проще:
// antipattern
var property = "name";
alert(eval("obj." + property));
// preferred
var property = "name";
alert(obj[property]);
С помощью eval()
также имеет значение для безопасности, потому что вы можете выполнять код (например, поступающий из сети), который был подделан. Это обычный антипаттерн при работе с JSON-ответом на запрос Ajax. В этих случаях лучше использовать встроенные в браузер методы для анализа ответа JSON, чтобы убедиться в его безопасности и допустимости. Для браузеров, которые не поддерживают JSON.parse()
изначально вы можете использовать библиотеку из JSON.org.
Также важно помнить, что передача строк в setInterval()
, setTimeout()
и Function()
Конструктор, по большей части, похож на использование eval()
и поэтому следует избегать.
За кулисами JavaScript все еще должен оценивать и выполнять строку, которую вы передаете как программный код:
// antipatterns
setTimeout("myFunc()", 1000);
setTimeout("myFunc(1, 2, 3)", 1000);
// preferred
setTimeout(myFunc, 1000);
setTimeout(function () {
myFunc(1, 2, 3);
}, 1000);
Использование нового конструктора Function() аналогично eval() и к нему следует подходить с осторожностью. Это может быть мощная конструкция, но часто используется не по назначению. Если вам абсолютно необходимо использовать eval()
вместо этого вы можете рассмотреть возможность использования новой функции ().
Есть небольшое потенциальное преимущество, потому что код, оцененный в new Function(), будет работать в локальной области функций, поэтому любые переменные, определенные с помощью var в оцениваемом коде, не станут автоматически глобальными.
Другой способ предотвратить автоматические глобальные переменные - это обернутьeval()
вызов в непосредственную функцию.
Это значительно снижает ваш уровень уверенности в безопасности.
Вывоз мусора
Сборщик мусора браузеров не знает, можно ли удалить код, который eval'ed, из памяти, поэтому он просто хранит его, пока страница не будет перезагружена. Неплохо, если ваши пользователи будут на вашей странице только в ближайшее время, но это может стать проблемой для веб-приложений.
Вот сценарий для демонстрации проблемы
https://jsfiddle.net/CynderRnAsh/qux1osnw/
document.getElementById("evalLeak").onclick = (e) => {
for(let x = 0; x < 100; x++) {
eval(x.toString());
}
};
Что-то столь же простое, как приведенный выше код, заставляет хранить небольшой объем памяти до тех пор, пока приложение не умрет. Это хуже, когда исключенный сценарий является гигантской функцией и вызывается с интервалом.
Помимо возможных проблем безопасности, если вы выполняете пользовательский код, в большинстве случаев есть лучший способ, который не предусматривает повторный анализ кода при каждом его выполнении. Анонимные функции или свойства объекта могут заменить большинство применений eval и намного безопаснее и быстрее.
eval() очень мощный и может использоваться для выполнения оператора JS или оценки выражения. Но вопрос не в том, как использовать eval(), а просто скажем, как злоумышленник влияет на строку, которую вы запускаете с помощью eval(). В конце вы будете использовать вредоносный код. С силой приходит большая ответственность. Так что используйте это с умом, если вы используете это. Это не сильно связано с функцией eval(), но эта статья содержит довольно хорошую информацию: http://blogs.popart.com/2009/07/javascript-injection-attacks/ Если вы ищете основы eval() смотрите здесь: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
Это может стать более серьезной проблемой, так как браузеры следующего поколения выпускают некоторые разновидности JavaScript-компилятора. Код, выполняемый через Eval, может не работать так же хорошо, как и остальная часть вашего JavaScript на этих новых браузерах. Кто-то должен сделать некоторое профилирование.
Это не всегда плохая идея. Взять, к примеру, генерацию кода. Недавно я написал библиотеку под названием Hyperbars, которая ликвидирует разрыв между virtual-dom и рулем. Это делается путем анализа шаблона руля и преобразования его в гиперсценарий, который впоследствии используется virtual-dom. Гиперскрипт сначала генерируется в виде строки, а затем возвращается, eval()
это превратить его в исполняемый код. я нашел eval()
в этой конкретной ситуации полная противоположность зла.
В основном из
<div>
{{#each names}}
<span>{{this}}</span>
{{/each}}
</div>
К этому
(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({}))
Производительность eval()
Это не проблема в такой ситуации, потому что вам нужно только один раз интерпретировать сгенерированную строку, а затем многократно использовать исполняемый вывод.
Вы можете увидеть, как была достигнута генерация кода, если вам интересно.
Механизм JavaScript имеет ряд оптимизаций производительности, которые он выполняет на этапе компиляции. Некоторые из них сводятся к возможности по существу статически анализировать код в процессе его лексического анализа и заранее определять, где находятся все объявления переменных и функций, так что требуется меньше усилий для разрешения идентификаторов во время выполнения.
Но если Механизм находит в коде eval (..), он, по сути, должен предположить, что вся его осведомленность о местоположении идентификатора может быть недействительной, потому что он не может точно знать, в какой момент лексинга, какой код вы можете передать eval (..) изменить лексическую область видимости или содержимое объекта, который вы можете передать, чтобы создать новую лексическую область видимости, к которой следует обращаться.
Другими словами, в пессимистическом смысле большинство тех оптимизаций, которые он сделал бы, бессмысленно, если присутствует eval (..), поэтому он просто не выполняет оптимизацию вообще.
Это все объясняет.
Ссылка:
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
Я бы сказал, что это не имеет значения, если вы используете eval()
в JavaScript, который запускается в браузерах.*(предостережение)
Все современные браузеры имеют консоль разработчика, где вы в любом случае можете выполнить произвольный javascript, и любой полуумный разработчик может взглянуть на ваш JS-источник и поместить все нужные ему кусочки в консоль разработчика, чтобы сделать то, что он хочет.
* До тех пор, пока конечные точки вашего сервера имеют правильную проверку и санацию предоставленных пользователем значений, не должно иметь значения, что анализируется и обрабатывается в вашем клиентском JavaScript.
Если бы вы спросили, подходит ли это для использования eval()
однако в PHP ответ НЕТ, если вы не внесете в белый список какие-либо значения, которые могут быть переданы в ваш оператор eval.
Это одна из хороших статей, рассказывающих об eval и о том, как он не является злом: http://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/
Я не говорю, что вы должны убежать и начать использовать eval() везде. На самом деле, очень мало хороших вариантов использования для запуска eval(). Определенно существуют проблемы с ясностью кода, отладкой и, конечно же, производительностью, которые не следует упускать из виду. Но вы не должны бояться использовать его, когда у вас есть случай, когда eval() имеет смысл. Попробуйте сначала не использовать его, но не позволяйте никому пугать вас, думая, что ваш код более хрупок или менее безопасен при правильном использовании eval().
Я не буду пытаться опровергнуть все, что было сказано до этого, но я предложу использовать eval(), который (насколько я знаю) не может быть реализован каким-либо другим способом. Вероятно, есть другие способы кодирования этого и, возможно, способы его оптимизации, но это делается от руки и без наворотов для ясности, чтобы проиллюстрировать использование eval, у которого действительно нет других альтернатив. То есть: динамические (или точнее) программно созданные имена объектов (в отличие от значений).
//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);
РЕДАКТИРОВАТЬ: кстати, я бы не советовал (по всем причинам безопасности, указанным выше), чтобы вы основали свои имена объектов на вводе пользователем. Я не могу представить себе вескую причину, по которой вы захотите это сделать. Тем не менее, я хотел бы указать, что это не будет хорошей идеей:)