什么时候JavaScript的eval()不是邪恶的?

时间:2008-10-13 14:28:29

标签: javascript coding-style eval

我正在编写一些JavaScript代码来解析用户输入的函数(用于类似电子表格的功能)。解析了公式后,我可以将其转换为JavaScript并在其上运行eval()以产生结果。

然而,我总是回避使用eval()如果我可以避免它,因为它是邪恶的(而且,无论是对还是错,我一直以为它在JavaScript中更加邪恶,因为代码到评估可能会被用户改变。

那么,什么时候可以使用呢?

26 个答案:

答案 0 :(得分:252)

我想花一点时间来解决你的问题的前提 - eval()是“ evil ”。编程语言人员使用的“ evil ”一词通常意味着“危险”,或者更确切地说“能够通过简单的命令造成大量伤害”。那么,什么时候可以使用危险的东西呢?当您知道危险是什么时,以及何时采取适当的预防措施。

关于这一点,我们来看看使用eval()的危险性。可能存在许多小的隐藏危险,就像其他一切一样,但是两个大的风险 - eval()被认为是邪恶的原因 - 是性能和代码注入。

  • 性能 - eval()运行解释器/编译器。如果你的代码是编译的,那么这是一个很大的问题,因为你需要在运行时调用一个可能很重的编译器。但是,JavaScript仍然主要是一种解释型语言,这意味着在一般情况下调用eval()并不是一个很大的性能损失(但请参阅下面我的具体说明)。
  • 代码注入 - eval()可能在提升的权限下运行一串代码。例如,以管理员/ root身份运行的程序永远不会想要eval()用户输入,因为该输入可能是“rm -rf / etc / important-file”或更糟。同样,浏览器中的JavaScript没有这个问题,因为程序无论如何都在用户自己的帐户中运行。服务器端JavaScript可能存在这个问题。

根据您的具体情况而定。根据我的理解,你自己生成字符串,所以假设你小心不要生成像“rm -rf something-important”这样的字符串,那么就没有代码注入风险(但请记住,它是非常非常努力以确保在一般情况下这样做。此外,如果你在浏览器中运行,那么代码注入是一个相当小的风险,我相信。

至于性能,你必须加重这一点,以免编码。我认为,如果你正在解析公式,你也可以在解析期间计算结果,而不是运行另一个解析器(eval()中的一个)。但是使用eval()进行编码可能更容易,并且性能损失可能不明显。看起来eval()在这种情况下并不比任何其他可以节省你一些时间的函数更邪恶。

答案 1 :(得分:71)

eval()不是邪恶的。或者,如果是这样,那就像反射,文件/网络I / O,线程和IPC在其他语言中是“邪恶的”一样是邪恶的。

如果出于您的目的eval()比手动解释更快,或者使您的代码更简单或更清晰......那么您应该使用它。如果不是,那么你不应该。就这么简单。

答案 2 :(得分:56)

当你信任来源时。

对于JSON,篡改源或多或少都很难,因为它来自您控制的Web服务器。只要JSON本身不包含用户上传的数据,使用eval就没有主要缺点。

在所有其他情况下,我会竭尽全力确保用户提供的数据符合我的规则,然后再将其提供给eval()。

答案 3 :(得分:23)

让我们成为真正的人:

  1. 现在每个主要的浏览器都有一个内置的控制台,你可能的黑客可以使用它来调用任何值的任何函数 - 为什么他们懒得使用eval语句 - 即使它们可以? / p>

  2. 如果编译2000行JavaScript需要0.2秒,如果我评估四行JSON,我的性能会下降吗?

  3. 即使是克罗克福德对'eval is evil'的解释也很薄弱。

       eval是邪恶的,eval函数是最被误用的特征   JavaScript的。避免它

    正如克罗克福德本人可能会说的那样“这种说法往往会导致非理性神经症。不要买它。”

    了解eval并了解它何时有用更为重要。例如,eval是评估软件生成的服务器响应的合理工具。

    BTW:Prototype.js直接调用eval五次(包括evalJSON()和evalResponse())。 jQuery在parseJSON中使用它(通过Function构造函数)。

答案 4 :(得分:17)

我倾向于关注eval() setTimeout(),并完全避免它。即使看起来需要它的方式也没有。例如,setTimeout(function() { alert('hi'); }, 1000); 允许您传递函数而不是eval。

{{1}}

即使它是受信任的源,我也不会使用它,因为JSON返回的代码可能会出现乱码,最多可能会做一些不好的事情,最坏的情况是暴露出坏事。< / p>

答案 5 :(得分:4)

我看到人们提倡不使用eval,因为 evil ,但我看到同样的人动态使用Function和setTimeout,所以他们在引擎盖下使用eval : d

顺便说一句,如果您的沙箱不够确定(例如,如果您正在使用允许代码注入的站点上工作),则eval是您的最后一个问题。安全性的基本规则是所有输入都是邪恶的,但是在JavaScript 甚至的情况下,JavaScript本身可能是邪恶的,因为在JavaScript中你可以覆盖任何函数,你就可以不确定你是否正在使用真实代码,因此,如果恶意代码在您之前启动,则您不能信任任何JavaScript内置函数:D

现在这篇文章的结语是:

如果您真的需要它(80%的时间eval 需要)并且您确定自己在做什么,只需使用eval(或更好的功能;)),闭包和OOP覆盖80/90%的情况,其中eval可以使用另一种逻辑替换,其余的是动态生成的代码(例如,如果你正在编写一个解释器)和你一样已经说过评估JSON(这里你可以使用Crockford安全评估;))

答案 6 :(得分:4)

在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的评估可能会在引用变量时失败应该是关闭的一部分,但实际上不是。

答案 7 :(得分:4)

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与否将几乎没有任何区别,

  1. 首先,浏览器在沙箱中调用整个脚本。
  2. 任何在EVAL中都是邪恶的代码,在浏览器本身都是邪恶的。攻击者或任何人都可以轻松地在DOM中注入脚本节点,并且如果他/她可以评估任何内容,则可以执行任何操作。不使用EVAL不会有任何区别。
  3. 主要是糟糕的服务器端安全性是有害的。服务器上的Cookie验证不佳或ACL实施不当会导致大多数攻击。
  4. Java的本机代码中存在最近的Java漏洞等。 JavaScript曾经被设计为在沙盒中运行,而applet则设计为在沙箱外运行,并带有证书等,从而导致漏洞和许多其他事情。
  5. 编写用于模仿浏览器的代码并不困难。您所要做的就是使用您最喜欢的用户代理字符串向服务器发出HTTP请求。无论如何,所有测试工具都会模拟浏如果攻击者想要伤害你,EVAL是他们的最后手段。他们还有许多其他方法来处理您的服务器端安全性。
  6. 浏览器DOM无权访问文件而不能访问用户名。事实上,eval可以访问的机器上没有任何内容。
  7. 如果您的服务器端安全性足够强大,任何人都可以从任何地方进行攻击,那么您不必担心EVAL。正如我所提到的,如果EVAL不存在,攻击者可以使用许多工具攻击您的服务器,而不管您的浏览器的EVAL功能如何。

    Eval仅适用于生成一些模板,以根据事先未使用的内容进行复杂的字符串处理。例如,我更喜欢

    "FirstName + ' ' + LastName"
    

    相反
    "LastName + ' ' + FirstName"
    

    作为我的显示名称,可以来自数据库,也不是硬编码。

答案 8 :(得分:3)

底线

如果您创建或清理了eval代码,那么它永远不会邪恶

稍详细

如果

eval邪恶的,如果它是使用客户端提交的输入在服务器上运行的,该输入不是由开发人员创建的,而是由开发人员创建的,则没有进行过消毒。由开发人员。

eval如果在客户端上运行,不是邪恶的即使使用客户端精心制作的未经消毒的输入

显然,您应该始终清除输入内容,以便对代码使用的内容进行一定的控制。

推理

即使开发人员未编写代码,客户端也可以运行他们想要的任何任意代码;不仅对于逃避了什么 ,而且对eval本身的调用都是如此。

答案 9 :(得分:3)

Microsoft解释了为什么eval()在IE博客上的浏览器速度慢, IE+JavaScript Performance Recommendations Part 2: JavaScript Code Inefficiencies

答案 10 :(得分:2)

当你需要使用eval()时,唯一的例子就是你需要动态运行动态JS。我在谈论你从服务器异步下载的JS ......

...... 10次中有9次你可以通过重构轻松避免这样做。

答案 11 :(得分:2)

如果您完全控制传递给eval函数的代码,则可以使用它。

答案 12 :(得分:1)

就客户端脚本而言,我认为安全问题是一个没有实际意义的问题。加载到浏览器中的所有内容都会受到操纵,因此应予以对待。当有更简单的方法来执行JavaScript代码和/或操纵DOM中的对象(例如浏览器中的URL栏)时,使用eval()语句没有任何风险。

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>

答案 13 :(得分:1)

我认为对eval进行辩护的任何案例都是很少的。您会以为它是有道理的,而不是在实际上是有道理的情况下使用它。

安全问题是最众所周知的。但也请注意,JavaScript使用JIT编译,因此与eval一起使用时效果很差。 Eval有点像编译器的黑匣子,而JavaScript需要能够(在某种程度上)提前预测代码,以便安全正确地应用性能优化和范围界定。在某些情况下,性能影响甚至会影响eval之外的其他代码。

如果您想了解更多信息: https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md#eval

答案 14 :(得分:1)

在服务器端,eval在处理外部脚本(如sql或Influxdb或mongo)时非常有用。可以在不重新部署服务的情况下在运行时进行自定义验证。

例如,具有以下元数据的成就服务

&#13;
&#13;
{
  "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\"}`"
  }
}
&#13;
&#13;
&#13;

然后允许,

  • 通过json中的文字字符串直接注入对象/值,对于模板化文本非常有用

  • 可以用作比较器,比如我们制定规则如何验证CMS中的任务或事件

Con of this:

  • 如果没有经过全面测试,可能是代码中的错误并破坏了服务中的内容。

  • 如果黑客可以在您的系统上编写脚本,那么您几乎搞砸了。

  • 验证脚本的一种方法是将脚本的哈希保存在安全的位置,以便在运行之前检查它们。

答案 15 :(得分:1)

我使用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;
}

答案 16 :(得分:1)

JavaScript的eval()何时不是邪恶的?

我总是试图discourage from using eval。几乎总是,提供更清洁和可维护的解决方案。评估is not needed even for JSON parsing。评估adds to maintenance hell。不是没有理由,道格拉斯·克罗克福德这样的大师不赞成。

但是我找到了一个应该使用的例子:

当您需要传递表达式时。

例如,我有一个为我构造一般google.maps.ImageMapType对象的函数,但是我需要告诉它配方,它应该如何从zoom和{{1}构建tile网址参数:

coord

答案 17 :(得分:1)

eval很少是正确的选择。虽然可能有许多实例可以通过将脚本连接在一起并动态运行来完成您需要完成的任务,但您通常可以使用更强大和可维护的技术:关联数组表示法(obj["prop"]obj.prop),闭包,面向对象技术,功能技术相同 - 改为使用它们。

答案 18 :(得分:0)

当您没有宏时,Eval对于代码生成很有用。

对于(一个愚蠢的)示例,如果您正在编写Brainfuck编译器,您可能希望构造一个以字符串形式执行指令序列的函数,并对其进行评估以返回函数。

答案 19 :(得分:0)

我认为eval是一个非常强大的功能,适用于客户端Web应用程序并且安全......与JavaScript一样安全,但不是。 :-)安全问题本质上是一个服务器端问题,因为现在,使用像Firebug这样的工具,你可以攻击任何JavaScript应用程序。

答案 20 :(得分:0)

代码生成。我最近写了一个名为Hyperbars的库,它弥合了virtual-domhandlebars之间的差距。它通过解析把手模板并将其转换为hyperscript来实现此目的。 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()的性能在这种情况下也不是问题,因为您只需要解释生成的字符串一次,然后多次重复使用可执行输出。

如果你好奇here,你可以看到如何实现代码生成。

答案 21 :(得分:0)

如果确实需要,eval不是邪恶的。但我偶然发现的99.9%的eval使用需要(不包括setTimeout东西)。

对我来说,邪恶不是表演,甚至不是安全问题(嗯,间接是两者兼而有之)。 eval的所有这些不必要的使用增加了维护地狱。重构工具被抛弃了。搜索代码很难。这些遗嘱的意外影响很大。

答案 22 :(得分:0)

只要您可以确定代码的来源来自您或实际用户,就没有理由不使用eval()。即使他可以操作发送到eval()函数的内容,这也不是安全问题,因为他能够操纵网站的源代码,因此可以更改JavaScript代码本身。

那么......何时不使用eval()?只有在第三方有可能改变它时才应使用Eval()。就像拦截客户端和服务器之间的连接一样(但如果这是一个问题,请使用HTTPS)。你不应该使用eval()来解析其他人在论坛中编写的代码。

答案 23 :(得分:0)

仅在测试期间,如果可能的话。另请注意,eval()比其他专门的JSON等评估器慢得多。

答案 24 :(得分:0)

Eval不是邪恶的,只是被滥用了。

如果您创建了包含在其中的代码或可以信任它,那么就可以了。 人们一直在谈论用户输入与eval无关紧要。很好〜

如果有用户输入发送到服务器,然后又返回到客户端,并且该代码在eval中被使用而没有被清除。恭喜,您已经打开了pandora的框,可以将用户数据发送给任何人。

根据评估的位置,许多网站都使用SPA,而eval可以使用户更轻松地访问应用程序内部,否则本来就不容易。现在,他们可以制作一个伪造的浏览器扩展程序,可以将其粘贴到该评估中并再次窃取数据。

仅需弄清楚您使用评估的意义是什么。当您仅可以创建方法来执行此类操作,使用对象等时,生成代码并不是真正理想的选择。

现在是使用eval的一个很好的例子。 您的服务器正在读取您创建的swagger文件。许多URL参数以{myParam}格式创建。因此,您希望读取URL,然后将它们转换为模板字符串,而不必进行复杂的替换,因为您有许多端点。所以你可以做这样的事情。 请注意,这是一个非常简单的示例。

const params = { id: 5 };

const route = '/api/user/{id}';
route.replace(/{/g, '${params.');

// use eval(route); to do something

答案 25 :(得分:-4)

当您使用解析函数(例如,jQuery.parseJSON)解析JSON结构时,它需要JSON文件的完美结构(每个属性名称都是双引号)。但是,JavaScript更灵活。因此,您可以使用eval()来避免它。