Когда JavaScript eval() не злой?

Я пишу код JavaScript для анализа введенных пользователем функций (для функций, подобных таблицам). Разработав формулу, я смог преобразовать ее в JavaScript и запустить eval(), чтобы получить результат.

Однако я всегда избегал использования eval(), если я могу избежать этого, потому что он злой (и, правильно или неправильно, я всегда думал, что это еще более злое в JavaScript, потому что код, который нужно оценить может быть изменен пользователем).

Итак, когда это нормально использовать?

Ответы

Ответ 1

Я хотел бы на минутку обратиться к предпосылке вашего вопроса - что eval() является "злым". Слово "зло", используемое людьми, говорящими на языке программирования, обычно означает "опасный", а точнее "способный причинить большой вред простой команде". Итак, когда можно использовать что-то опасное? Когда вы знаете, что такое опасность, и когда вы принимаете соответствующие меры предосторожности.

В связи с этим рассмотрим опасности использования eval(). Вероятно, существует много мелких скрытых опасностей, как и все остальное, но два больших риска - причина, по которой eval() считается злой, - это производительность и ввод кода.

  • Производительность - eval() запускает интерпретатор/компилятор. Если ваш код скомпилирован, то это большой успех, потому что вам нужно вызвать возможно-тяжелый компилятор в середине времени выполнения. Однако JavaScript по-прежнему в основном является интерпретированным языком, а это означает, что вызов eval() не является большим успехом в общем случае (но см. Мои конкретные замечания ниже).
  • Ввод кода - eval() потенциально запускает строку кода с повышенными привилегиями. Например, программа, выполняемая как администратор /root, никогда не захочет вводить пользователя eval(), потому что этот вход потенциально может быть "rm -rf/etc/important-file" или хуже. Опять же, JavaScript в браузере не имеет этой проблемы, потому что программа работает в учетной записи пользователя в любом случае. На стороне сервера JavaScript может возникнуть проблема.

В вашем конкретном случае. Из того, что я понимаю, вы сами генерируете строки, поэтому, полагая, что вы не допускаете создания строки типа "rm -rf something-important", нет риска инъекции кода (но, пожалуйста, помните, что это очень трудно обеспечить это в общем случае). Кроме того, если вы работаете в браузере, я думаю, что инъекция кода является довольно небольшим риском.

Что касается производительности, вам придется весить это от простоты кодирования. По моему мнению, если вы разбираете формулу, вы можете также вычислить результат во время разбора, а не запускать другой парсер (один внутри eval()). Но с помощью eval() может быть проще скомпилировать код, и поражение производительности, вероятно, будет незаметным. Похоже, что eval() в этом случае не более зла, чем любая другая функция, которая могла бы сэкономить вам некоторое время.

Ответ 2

eval() не является злом. Или, если это так, это зло так же, как отражение, файловый/сетевой ввод-вывод, потоки и IPC являются "злыми" на других языках.

Если для вашей цели eval() работает быстрее, чем ручная интерпретация, или делает ваш код более простым или более понятным... тогда вы должны его использовать. Если нет, то не стоит. Просто как это.

Ответ 3

Когда вы доверяете источнику.

В случае JSON, более или менее трудно вмешиваться в исходный код, поскольку он исходит от веб-сервера, которым вы управляете. Пока сам JSON не содержит данных, которые пользователь загрузил, нет никакого существенного недостатка в использовании eval.

Во всех остальных случаях я бы сделал большие длины, чтобы гарантировать, что данные, предоставленные пользователем, соответствуют моим правилам, прежде чем подавать их на eval().

Ответ 4

Позвольте получить настоящие люди:

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

  2. Если для компиляции 2000 строк JavaScript требуется 0,2 секунды, какова будет моя производительность, если я получу четыре строки JSON?

Даже объяснение Крокфорда "зло - это зло" слабо.

eval is Evil, функция eval является наиболее неправильно используемой функцией JavaScript. Избегайте этого

Как мог бы сам Крокфорд сказать: "Подобные высказывания имеют тенденцию вызывать иррациональный невроз. Не покупайте его".

Понимание eval и знание того, когда это может быть полезно, гораздо важнее. Например, eval - это разумный инструмент для оценки ответов сервера, сгенерированных вашим программным обеспечением.

Кстати, Prototype.js вызывает eval напрямую пять раз (в том числе в evalJSON() и evalResponse()). jQuery использует его в parseJSON (через конструктор функций).

Ответ 5

Я склонен следовать совет Крокфорда за eval() и вообще избегать этого. Даже пути, которые, как представляется, требуют этого, не делают. Например, setTimeout() позволяет вам передать функцию, а не eval.

setTimeout(function() {
  alert('hi');
}, 1000);

Даже если это надежный источник, я его не использую, потому что код, возвращаемый JSON, может быть искажен, что в лучшем случае может сделать что-то неуловимое, в худшем случае, показать что-то плохое.

Ответ 6

Я видел, как люди выступают за то, чтобы не использовать eval, потому что это зло, но я видел, как те же люди использовали функцию Function и setTimeout динамически, поэтому они используют eval под капотами: D

Кстати, если ваша песочница не совсем уверена (например, если вы работаете на сайте, который позволяет вводить код), eval - это последняя из ваших проблем. Основное правило безопасности заключается в том, что все входные данные являются злыми, но в случае JavaScript даже сам JavaScript может быть злым, потому что в JavaScript вы можете перезаписать любую функцию, и вы просто не можете быть уверены, что используете реальный, если вредоносный код запускается перед вами, вы не можете доверять встроенной функции JavaScript: D

Теперь эпилог этого сообщения:

Если вам нужен ДЕЙСТВИТЕЛЬНО (80% времени eval НЕ), и вы уверены в том, что делаете, просто используйте eval (или лучшая функция;)), замыкания и ООП охватывают 80/90% случая, когда eval можно заменить с помощью другого типа логики, остальное - это динамически сгенерированный код (например, если вы пишете переводчика) и как вы уже сказал, оценивая JSON (здесь вы можете использовать безопасную оценку Crockford;))

Ответ 7

Eval дополняет компиляцию, которая используется при шаблонизации кода. По шаблонам я подразумеваю, что вы пишете упрощенный генератор шаблонов, который генерирует полезный код шаблона, который увеличивает скорость разработки.

Я написал фреймворк, в котором разработчики не используют EVAL, но они используют нашу фреймворк и, в свою очередь, эта платформа должна использовать EVAL для генерации шаблонов.

Производительность EVAL может быть увеличена с использованием следующего метода; вместо выполнения script, вы должны вернуть функцию.

var a = eval("3 + 5");

Он должен быть организован как

var f = eval("(function(a,b) { return a + b; })");

var a = f(3,5);

Кэширование f, безусловно, улучшит скорость.

Также Chrome позволяет легко отлаживать такие функции.

Что касается безопасности, использование eval или нет вряд ли имеет значение,

  • Прежде всего, браузер вызывает весь script в песочнице.
  • Любой код, который является злом в EVAL, является злом в самом браузере. Злоумышленник или любой человек может легко ввести script node в DOM и сделать что-нибудь, если он/она может что-либо сделать. Не использовать EVAL не будет иметь никакого значения.
  • В основном это опасная защита на стороне сервера. Причиной большинства атак является недостаточная проверка cookie или неудачная реализация ACL на сервере.
  • Недавняя уязвимость Java и т.д. находилась в Java-коде. JavaScript был и предназначен для работы в песочнице, тогда как апплеты были разработаны для работы за пределами изолированной программы с сертификатами и т.д., Что приводит к уязвимостям и многим другим вещам.
  • Написание кода для имитации браузера не сложно. Все, что вам нужно сделать, это сделать HTTP-запрос на сервер с помощью вашей любимой строки пользовательского агента. В любом случае все инструменты для тестирования мошенничают с браузерами; если злоумышленник хочет нанести вам вред, EVAL является их последним средством. У них есть много других способов решения вашей серверной безопасности.
  • У браузера DOM нет доступа к файлам, а не к имени пользователя. На самом деле ничто на машине, к которой может предоставить доступ eval.

Если ваша защита на стороне сервера достаточно прочная, чтобы кто-либо мог атаковать из любого места, вы не должны беспокоиться об EVAL. Как я уже упоминал, если EVAL не существует, у злоумышленников есть много инструментов для взлома вашего сервера независимо от возможностей вашего браузера EVAL.

Eval полезен только для создания некоторых шаблонов для сложной обработки строк на основе того, что не используется заранее. Например, я предпочту

"FirstName + ' ' + LastName"

В отличие от

"LastName + ' ' + FirstName"

Как мое отображаемое имя, которое может поступать из базы данных и которое не является жестко запрограммированным.

Ответ 8

При отладке в Chrome (v28.0.1500.72) я обнаружил, что переменные не привязаны к закрытию, если они не используются во вложенной функции, которая вызывает закрытие. Я думаю, что оптимизация движка JavaScript.

НО: когда eval() используется внутри функции, которая вызывает закрытие, ВСЕ, переменные внешних функций привязаны к замыканию, даже если они не являются используется вообще. Если у кого-то есть время, чтобы проверить, может ли быть произведена утечка памяти, пожалуйста, оставьте мне комментарий ниже.

Здесь мой тестовый код:

(function () {
    var eval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();   // Variable "unused" is visible in debugger
            eval("1");
        })();
    }

    evalTest();
})();

(function () {
    var eval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();   // Variable "unused" is NOT visible in debugger
            var noval = eval;
            noval("1");
        })();
    }

    evalTest();
})();

(function () {
    var noval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();    // Variable "unused" is NOT visible in debugger
            noval("1");
        })();
    }

    evalTest();
})();

Здесь я хотел бы отметить, что eval() не обязательно должен ссылаться на встроенную функцию eval(). Все зависит от имени функции. Поэтому при вызове native eval() с псевдонимом (скажем var noval = eval;, а затем во внутренней функции noval(expression);) оценка expression может завершиться неудачей, когда она ссылается на переменные, которые должны быть частью закрытия, но на самом деле нет.

Ответ 10

Итог

Если вы создали или очистили код, который вы eval, он никогда не будет злым.

Чуть подробнее

eval является злым, если работает на сервере, используя входные данные, отправленные клиентом, которые не были созданы разработчиком или не были очищены разработчиком.

eval не является злым, если работает на клиенте, , даже если используется неанизированный ввод, созданный клиентом.

Очевидно, что вы всегда должны очищать входные данные, чтобы иметь некоторый контроль над тем, что потребляет ваш код.

Рассуждая

Клиент может выполнить любой произвольный код, который он хочет, даже если разработчик не кодировал его; Это верно не только для того, что уклоняется, но и для вызова самого eval.

Ответ 11

Единственный экземпляр, когда вы должны использовать eval(), - это когда вам нужно запускать динамическое JS на лету. Я говорю о JS, который вы загружаете асинхронно с сервера...

... И 9 раз из 10 вы могли бы легко избежать этого путем рефакторинга.

Ответ 12

Это нормально использовать, если у вас есть полный контроль над кодом, переданным функции eval.

Ответ 13

eval редко является правильным выбором. Хотя может быть множество случаев, когда вы можете выполнить то, что вам нужно выполнить, объединив script вместе и запустив его на лету, у вас обычно есть гораздо более мощные и поддерживаемые методы: ассоциативное обозначение массива (obj["prop"] такая же, как obj.prop), замыкания, объектно-ориентированные методы, функциональные методы - используйте их вместо этого.

Ответ 14

На стороне сервера eval полезен при работе с внешними скриптами, такими как sql или influxdb или mongo. Если пользовательская проверка во время выполнения может быть выполнена без повторного развертывания ваших служб.

Например, служба достижения со следующими метаданными

  {
 Msgstr "" 
   msg_enum ":" квест/регистрация   ",
   "  своевременный ":" all_times  ",
   "  объем ": [
     " Квесты/ежедневно активные "  ],
   " query ":" ` SELECT COUNT (point) AS valid from\ "$ {userId}/dump/quest/daily-active \" LIMIT 1`   ",
   "  validator ":" valid > 0  ",
   "  reward_external ":" ewallet  ",
   "  reward_external_payload ":" {\ "token\": \ "${token}\", \ "userId\": \ "${userId}\", \ "amountIn\": 1, \ "conversionType\":\ "квест/регистрация: серебро \", \ "exchangeProvider \":\ "поставщик/достижение \", \ "exchangeType \":\ "платеж/квест/регистрация \" } ` "}," efdfb506-1234-abcd-9d4a-7d624c564332": {
   msg_enum ":" quest/daily-active   ",
   "  своевременный ":" ежедневно  ",
   "  объем ": [
     " Квесты/ежедневно активные "  ],
   " query ":" SELECT COUNT (point) AS valid from \" ${userId}/dump/quest/daily-active\"WHERE time > = '$ {today}' ${ENV.DAILY_OFFSET} LIMIT 1`   ",
   "  validator ":" valid > 0  ",
   "  reward_external ":" ewallet  ",
   "  reward_external_payload ":" {\ "token\": \ "${token}\", \ "userId\": \ "${userId}\", \ "amountIn\": 1, \ "conversionType\":\"quest/daily-active: silver \",\ "exchangeProvider \":\ "поставщик/достижение \", \ "exchangeType \":\"payment/quest/daily-active \ "} `" 
 }
}Код>

Ответ 15

Я думаю, что любые случаи оправданности eval были бы редкостью. Вы, скорее всего, будете использовать его, думая, что это оправдано, чем использовать его, когда это действительно оправдано.

Вопросы безопасности являются наиболее известными. Но также имейте в виду, что JavaScript использует JIT-компиляцию, и это плохо работает с eval. Eval чем-то напоминает "черный ящик" для компилятора, и JavaScript должен уметь предсказывать код заранее (до некоторой степени), чтобы безопасно и правильно применять оптимизацию производительности и область видимости. В некоторых случаях влияние на производительность может даже повлиять на другой код за пределами eval.

Если вы хотите узнать больше:https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md#eval

Ответ 16

Только во время тестирования, если это возможно. Также обратите внимание, что eval() намного медленнее, чем другие специализированные оценщики JSON и т.д.

Ответ 17

Нет причин не использовать eval(), если вы можете быть уверены, что источник кода исходит от вас или от фактического пользователя. Несмотря на то, что он может манипулировать тем, что отправляется в функцию eval(), это не проблема безопасности, поскольку он способен манипулировать исходным кодом веб-сайта и поэтому может изменить сам код JavaScript.

Итак... когда не использовать eval()? Eval() следует использовать только тогда, когда есть вероятность, что третья сторона может ее изменить. Подобно перехвату соединения между клиентом и вашим сервером (но если это проблема, используйте HTTPS). Вы не должны eval() для анализа кода, написанного другими, например, на форуме.

Ответ 18

Если это действительно необходимо, это не зло. Но 99,9% использования eval, с которым я сталкиваюсь, - это не (не включая материал setTimeout).

Для меня зло - это не производительность или даже проблема безопасности (ну, косвенно, это и то, и другое). Все такие ненужные использования eval добавляют к адскому телу. Инструменты рефакторинга отбрасываются. Поиск кода затруднен. Неожиданными последствиями этих оценок являются легионы.

Ответ 19

Что касается клиента script, я думаю, что проблема безопасности - спорный вопрос. Все, загруженные в браузер, подвергаются манипуляциям и должны рассматриваться как таковые. Существует неопасность при использовании инструкции eval(), когда есть намного более простые способы выполнения кода JavaScript и/или манипулирования объектами в DOM, например, в строке URL в вашем браузере.

javascript:alert("hello");

Если кто-то хочет манипулировать их DOM, я говорю, что он отмахивается. Безопасность для предотвращения любого типа атаки всегда должна отвечать серверному приложению, периоду.

С прагматичной точки зрения, нет никакой пользы от использования eval() в ситуации, когда все может быть сделано иначе. Однако существуют конкретные случаи, когда EVAL следует использовать. Когда это так, это может быть сделано без риска взрыва страницы.

<html>
    <body>
        <textarea id="output"></textarea><br/>
        <input type="text" id="input" />
        <button id="button" onclick="execute()">eval</button>

        <script type="text/javascript">
            var execute = function(){
                var inputEl = document.getElementById('input');
                var toEval = inputEl.value;
                var outputEl = document.getElementById('output');
                var output = "";

                try {
                    output = eval(toEval);
                }
                catch(err){
                    for(var key in err){
                        output += key + ": " + err[key] + "\r\n";
                    }
                }
                outputEl.value = output;
            }
        </script>
    <body>
</html>

Ответ 20

Когда JavaScript eval() не злой?

Я всегда пытаюсь отказать в использовании eval. Почти всегда доступно более чистое и поддерживаемое решение. Eval не нужен даже для разбора JSON. Eval добавляет в адский сервис. Недаром на него нахмурились мастера, такие как Дуглас Крокфорд.

Но я нашел один пример, где он должен использоваться:

Когда вам нужно передать выражение.

Например, у меня есть функция, которая создает для меня общий объект google.maps.ImageMapType, но мне нужно сказать ему рецепт, как он должен построить URL-адрес плитки из параметров zoom и coord:

my_func({
    name: "OSM",
    tileURLexpr: '"http://tile.openstreetmap.org/"+b+"/"+a.x+"/"+a.y+".png"',
    ...
});

function my_func(opts)
{
    return new google.maps.ImageMapType({
        getTileUrl: function (coord, zoom) {
            var b = zoom;
            var a = coord;
            return eval(opts.tileURLexpr);
        },
        ....
    });
}

Ответ 21

Мой пример использования eval: import.

Как это обычно делается.

var components = require('components');
var Button = components.Button;
var ComboBox = components.ComboBox;
var CheckBox = components.CheckBox;
...
// That quickly gets very boring

Но с помощью eval и небольшой вспомогательной функции он выглядит намного лучше:

var components = require('components');
eval(importable('components', 'Button', 'ComboBox', 'CheckBox', ...));

importable может выглядеть (эта версия не поддерживает импорт конкретных элементов).

function importable(path) {
    var name;
    var pkg = eval(path);
    var result = '\n';

    for (name in pkg) {
        result += 'if (name !== undefined) throw "import error: name already exists";\n'.replace(/name/g, name);
    }

    for (name in pkg) {
        result += 'var name = path.name;\n'.replace(/name/g, name).replace('path', path);
    }
    return result;
}

Ответ 22

Я убежден, что eval - очень мощная функция для клиентских веб-приложений и безопасна... Насколько это безопасно, как JavaScript, чего нет.:-) Проблемы с безопасностью по существу являются проблемой на стороне сервера, поскольку теперь с помощью инструмента Firebug вы можете атаковать любое приложение JavaScript.

Ответ 23

Eval полезен для генерации кода, когда у вас нет макросов.

Для (глупый) пример, если вы пишете компилятор Brainfuck, вы, вероятно, захотите построить функцию который выполняет последовательность инструкций в виде строки и eval для возврата функции.

Ответ 24

Генерация кода. Недавно я написал библиотеку под названием Hyperbars, которая устраняет разрыв между virtual-dom и handlebars. Он делает это путем анализа шаблона руля и преобразования его в hyperscript. Гиперссылка генерируется как строка сначала и перед ее возвратом, 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() не является проблемой в подобной ситуации, потому что вам нужно только один раз интерпретировать сгенерированную строку, а затем повторно использовать исполняемый вывод многократно.

Вы можете видеть, как генерация кода была достигнута, если вам любопытно здесь.

Ответ 25

Когда вы разбираете структуру JSON с помощью функции синтаксического анализа (например, jQuery.parseJSON), она ожидает идеальную структуру файла JSON (каждое имя свойства находится в двойных кавычках). Однако JavaScript более гибкий. Поэтому вы можете использовать eval(), чтобы избежать этого.