Вопрос

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

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

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

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

Давайте познакомимся с реальными людьми.

<Ол>
  • В каждом крупном браузере теперь есть встроенная консоль, которую ваш потенциальный хакер может с избытком использовать для вызова любой функции с любым значением - зачем им пытаться использовать оператор eval - даже если бы они могли?

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

  • Даже объяснение Крокфорда 'eval is evil' слабо.

      

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

    Как мог бы сам Крокфорд сказать: «Такое утверждение порождает иррациональный невроз. Не покупайте это. & Quot;

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

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

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

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

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

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

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

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

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

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

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

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

    Кроме того, Chrome позволяет очень легко отлаживать такие функции.

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

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

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

    "FirstName + ' ' + LastName"
    

    В отличие от

    "LastName + ' ' + FirstName"
    

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

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

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

    Теперь эпилог к этому посту таков:

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

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

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

    Вот мой тестовый код:

    (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 () . Все зависит от названия функции . Так что при вызове нативного eval () с псевдонимом (скажем, var noval = eval; и затем во внутренней функции noval (expression); ) тогда оценка expression может завершиться неудачей, когда она ссылается на переменные, которые должны быть частью замыкания, но на самом деле это не так.

    Microsoft объясняет, почему eval () работает медленно в их браузере в блоге IE, Рекомендации по повышению производительности IE + JavaScript, часть 2. Неэффективность кода JavaScript .

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

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

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

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

    Что касается клиентского скрипта, я думаю, что вопрос безопасности является спорным вопросом. Все, что загружено в браузер, подвергается манипуляциям и должно рассматриваться как таковое. Использование оператора 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>
    

    Когда eval () в JavaScript не является злом?

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

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

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

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

    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);
            },
            ....
        });
    }
    

    Мой пример использования 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;
    }
    

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

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

    {
      "568ff113-abcd-f123-84c5-871fe2007cf0": {
        "msg_enum": "quest/registration",
        "timely": "all_times",
        "scope": [
          "quest/daily-active"
        ],
        "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\": \"quest/registration:silver\", \"exchangeProvider\":\"provider/achievement\",\"exchangeType\":\"payment/quest/registration\"}`"
      },
      "efdfb506-1234-abcd-9d4a-7d624c564332": {
        "msg_enum": "quest/daily-active",
        "timely": "daily",
        "scope": [
          "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\":\"provider/achievement\",\"exchangeType\":\"payment/quest/daily-active\"}`"
      }
    }

    Которые затем позволяют,

    • Прямое внедрение объекта / значений через литеральную строку в json, полезное для создания шаблонов текстов

    • Можно использовать в качестве компаратора, скажем, мы создаем правила проверки квестов или событий в CMS

    Против этого:

    • Могут быть ошибки в коде и сбои в работе сервиса, если они не протестированы полностью.

    • Если хакер может написать скрипт в вашей системе, то вы в значительной степени облажались.

    • Один из способов проверить ваш скрипт - сохранить хэш ваших скриптов в надежном месте, чтобы вы могли проверить их перед запуском.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top