JavaScript的eval()函数在什么情况下不是邪恶的?

303
我正在编写一些JavaScript代码来解析用户输入的函数(用于类似电子表格的功能)。解析了公式后,我可以将其转换为JavaScript并对其运行eval()以得出结果。
然而,如果可以避免使用eval(),我总是回避它,因为它是有害的(而且,正确或错误地说,我一直认为在JavaScript中它甚至更加有害,因为要评估的代码可能会被用户更改)。
那么,在什么情况下可以使用它呢?

7
大多数 JSON 库实际上并没有在底层使用 eval,这样做正是为了防止安全风险。 - Sean McMillan
13
@Sean - JQuery 和 Prototype 都使用 eval(JQuery 使用 new Function) - plodder
8
@plodder - 你获取信息的来源是什么?自2010年1月以来,jQuery已经使用原生的JSON.parse()函数,可以自行查看:http://code.jquery.com/jquery-1.4.js。 - ken
4
“显然,必须使用eval()来解析JSON”——这并不正确,相反地-我们*不应该使用eval来解析JSON!*使用Douglas Crockford(JSON的创作者)在http://www.json.org/上提供的json2.js脚本! - Tomas
17
讽刺的是,json2.js使用eval来解析JSON。 - tobyodavies
显示剩余10条评论
27个回答

285
我想花一点时间来讨论您提出的问题前提 - eval()是“ evil ”。编程人员使用“ evil ”这个词通常意味着“危险”,更确切地说是“能够用一个看似简单的命令造成大量伤害”。那么,什么时候使用危险内容是可以接受的呢?当您知道危险性质并采取适当的预防措施时。
重点来了,让我们看看使用eval()存在的危险。可能有很多像其他所有东西一样的小隐患,但是两个主要的风险 - eval()被认为是“恶魔”的原因 - 是性能和代码注入。
  • 性能 - eval()运行解释器/编译器。如果您的代码已经被编译,则这是一个大打击,因为您需要在运行时调用一个可能非常繁重的编译器。然而,JavaScript仍然基本上是一种解释型语言,这意味着调用eval()在一般情况下不会对性能造成太大影响(但请参见我以下的具体评论)。
  • 代码注入 - eval()可能以提高的权限运行代码字符串。例如,作为管理员/根用户运行的程序永远不会想要eval()用户输入,因为该输入可能是“rm -rf /etc/important-file”或更糟。同样,浏览器中的JavaScript并没有这个问题,因为程序正在用户自己的帐户中运行。服务器端的JavaScript可能会遇到这个问题。

接下来谈谈您的具体情况。根据我所了解的,您自己生成字符串,因此假设您小心翼翼地不允许生成像“rm -rf something-important”这样的字符串,则不存在代码注入风险(但请记住,在一般情况下确保这一点非常非常困难)。而且,如果您在浏览器中运行,则代码注入的风险相对较小,我相信。

就性能而言,您需要权衡其与编码的易用性。在我看来,如果你正在解析这个公式,那么最好在解析过程中计算结果,而不是运行另一个解析器(eval()内部的解析器)。但使用eval()可能更容易编码,而且性能损失可能不会被注意到。在这种情况下,看起来eval()并不比其他可能为您节省时间的任何函数更邪恶。


83
您没有解决使用 eval 函数的代码难以调试的问题。 - bobobobo
57
如果你关心用户数据,那么代码注入对JavaScript来说是一个非常严重的问题。注入的代码会在浏览器中运行,就好像它来自于你的网站一样,让它能够执行用户可以手动执行的任何恶意操作。如果你允许第三方代码进入你的页面,它们可以代表你的客户下订单或更改他们的Gravatar头像,或者通过你的网站进行其他任何操作。要非常小心。让黑客接管你的客户和接管你的服务器一样糟糕。 - Sean McMillan
77
如果数据来自你的服务器并且是开发人员生成的,使用eval()没有任何伤害。真正有害的是相信你所读到的一切。你会看到很多人说eval()是邪恶的,但他们不知道为什么除了在某个地方读到过。 - Razor
48
我希望相信你,但是如果有人要拦截和更改从你的服务器发送到 eval() 的 JavaScript 代码,他们也可以在一开始就更改页面源代码,并控制用户的信息...我看不出有什么区别。 - Walt W
23
您是在询问“代码注入 - ...再次,浏览器中的JavaScript没有这个问题”,以及“而且,如果您在浏览器中运行,则代码注入风险相当小,我认为。” 您是否暗示在浏览器中的代码注入不是一个问题?事实上,跨站脚本攻击(XSS)已经连续数年位列OWASP十大漏洞列表的前三名。 - Mike Samuel
显示剩余18条评论

81

eval() 不是恶行。如果它是恶行的话,那么在其他语言中反射、文件/网络 I/O、线程和 IPC 也是“恶行”。

如果在你的情况下 eval() 比手动解析更快,或者使你的代码更简单、更清晰……那么你应该使用它。如果没有,则不应该使用。就这么简单。


6
其中一个目的可能是生成优化的代码,这样的代码要么太长,要么太重复,手写起来比较困难。在LISP中,这种情况通常需要用宏来实现。 - wberry
9
这是如此一般性的建议,以至于它可以应用于任何已经存在的代码块。它真的没有为这个问题增加任何内容;特别是,它并不能帮助任何来到这里的人确定他们的特定用法是否有问题。 - jpmc26
7
更快、更简单、更清晰……但这个答案没有很好地涵盖安全方面的影响。 - Ruud Helderman
这个建议确实增加了对任何工具“邪恶”的前提进行质疑的问题。因此,我会点赞。 - capr

65

当您信任数据源时。

在JSON的情况下,由于它来自您控制的web服务器,因此更难篡改源数据。只要JSON本身不包含用户上传的数据,使用eval函数并没有太大的弊端。

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


14
在使用eval()之前,应始终针对json语法测试json字符串。因此,json字符串“{foo:alert('XSS')}”不会通过测试,因为“alert('XSS')”不是一个正确的值。 - Gumbo
或者当环境是安全的时候。 - Eli Grey
3
那就使用HTTPS。另一方面,中间人攻击不是常见的普通网络应用程序的攻击场景,而跨站脚本攻击则是。 - Tomalak
7
eval 方法也不能正确解析所有有效的 JSON 字符串。例如,JSON.parse(' "\u2028" ') === "\u2028",但是 eval(' "\u2028" ') 会引发异常,因为在 JavaScript 中 U+2028 是一个换行符,但在 JSON 中它不是一个换行符。 - Mike Samuel
2
@Justin - 如果协议被破坏了,那么通常初始页面加载也会通过同一协议发送,这时候就没有意义了,因为客户端已经尽可能地被破坏了。 - antinome
显示剩余3条评论

26

让我们认真面对:

  1. 现在每个主流浏览器都有内置控制台,黑客可以大量使用它来调用任何函数和任何值 - 他们为什么要费劲地使用 eval 语句 - 即使他们能做到呢?

  2. 如果编译2000行JavaScript代码需要0.2秒,那么如果我评估四行JSON,我的性能损失是多少?

即使是 Crockford 对 "eval 是邪恶的" 的解释也很弱。

eval 是邪恶的,eval 函数是 JavaScript 中被滥用最多的功能。避免使用它

正如 Crockford 自己可能会说的那样,“这种说法往往会引发非理性的神经质。不要相信它。”

了解 eval 并知道何时可能有用更为重要。例如,eval 是用于评估由您的软件生成的服务器响应的明智工具。

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


10
如果可用的话,jQuery会使用浏览器内置的JSON.parse函数(速度更快、更安全),仅在没有时才使用eval作为后备机制。"eval是邪恶的"这个说法是一个相当不错的指导方针。 - jjmontes
37
每个主流浏览器现在都有一个内置控制台...当一个用户可以输入代码,然后在另一个用户的浏览器中运行该代码时,代码注入就会成为一个问题。浏览器控制台本身并不能允许一个用户在另一个用户的浏览器中运行代码,因此在决定是否值得防止代码注入时,它们是无关紧要的。 - Mike Samuel
35
每个主要的浏览器现在都有一个内置的控制台...他们为什么还要使用eval语句呢?- 你完全错了。我建议你编辑答案。一个用户能够注入可以在另一个用户的浏览器中运行的代码是一个重大问题。这就是你需要真正认真对待的地方。 - akkishore
7
@akkishore,如果您能提供一个现实生活中的例子来支持您过度陈述的论点,我将不胜感激。 - Akash Kava
10
你没有意识到的是,如果我在评论框中提交javascript并且该javascript被存储到数据库中,当另一个用户查看我放置javascript的评论时,eval函数将在呈现时使用解释器对该javascript进行评估,并导致我的javascript在其他用户的浏览器上执行。通过这种方式,我可以收集各种信息,如他们的用户名、数据库中的用户ID、电子邮件地址等。这不是一个困难的问题,如果你谷歌搜索XSS,你将在大约10秒钟内看到为什么这是一个问题。 - Kyle Richter
显示剩余13条评论

20

我倾向于遵循 Crockford的建议,尽量避免使用eval()。即使有看起来需要使用它的方式,也可以用其他方法代替。例如,setTimeout() 允许你传递一个函数而不是 eval。

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

即使是一个可信任的来源,我也不使用它,因为JSON返回的代码可能会乱码,最好情况下会有一些奇怪的事情发生,最坏的情况是暴露出一些糟糕的东西。


3
我认为服务器端JSON格式化程序中的错误肯定是一个问题。服务器的响应是否依赖于任何类型的用户提交的文本?那么你必须注意XSS。 - swilliams
4
如果您的web服务器没有通过HTTPS进行身份验证,那么可能会遭受中间人攻击,即另一台主机拦截请求并发送自己的数据。 - Ben Combee
12
如果有人可以进行中间人攻击,他可以轻松地向您的脚本中注入任何内容。 - el.pescado - нет войне
12
你不应该完全依赖于你的JavaScript代码... 不要依赖任何在客户端运行的东西... 如果有人进行中间人攻击,为什么他会去破坏你的JSON对象呢?他可以向你提供不同的网页和不同的JS文件... - Calmarius
7
我个人不喜欢那种说法:“总有其他方法可以做到的”。比如,你也可以说总有避免使用面向对象编程的方法。但这并不意味着它不是一种很棒的选择。如果你了解eval及其危险性,在正确的情况下使用它可以成为一个很好的工具。 - dallin
显示剩余2条评论

8

Bottom Line

如果您创建或清理了您使用eval的代码,则它永远不会是恶意的

稍微详细一些

如果在服务器上运行时,eval使用由未经过开发者创建或未经过开发者清理的客户端提交的输入,则eval恶意的

如果在客户端上运行,即使使用由客户端编写的未经过清理的输入,eval不是恶意的

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

原因

客户端可以运行他们想要的任何任意代码,即使开发人员没有编写它;这不仅适用于被评估的内容,也适用于调用eval本身


1
如果在客户端上运行,即使使用由客户端制作的未经过滤的输入,eval也不是邪恶的。但这并不正确。如果A人制作了一个脚本,在B人的客户端上运行eval,他们可以做一些像将B人的cookies发送到A人的远程服务器之类的事情。 - Deanveloper
任何人都可以在客户端上运行他们选择的任何代码,而不管开发者编写了什么。即使是卫生检查也可以通过浏览器插件和手动更改来删除。 - Steven Spungin
2
这完全不是真的。否则,XSS就不会成为安全漏洞了。实际上,我的意思是eval和设置innerHTML一样容易引起安全漏洞。只要你知道自己在做什么,就没问题,但如果不小心,它可能会打开一些攻击的大门(例如窃取cookie)。 - Deanveloper

8

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

  1. 首先,浏览器在沙箱中调用整个脚本。
  2. 在EVAL中的任何恶意代码,在浏览器本身中都是恶意的。如果攻击者或任何人可以评估任何内容,则可以轻松地在DOM中注入脚本节点并执行任何操作。不使用EVAL不会有任何区别。
  3. 大多数情况下,是服务器端安全性差才会造成危害。服务器端的Cookie验证或ACL实现较差会导致大多数攻击。
  4. 最近Java存在漏洞等问题。JavaScript被设计为在沙箱中运行,而小程序则被设计为在具有证书等沙箱之外运行,从而导致漏洞和其他问题。
  5. 编写模拟浏览器的代码并不困难。你只需要使用你喜欢的用户代理字符串向服务器发送HTTP请求。所有测试工具都会模拟浏览器;如果攻击者想要伤害你,EVAL是他们的最后手段。他们有许多其他方法来处理你的服务器端安全性。
  6. 浏览器DOM无法访问文件和用户名。事实上,eval无法访问机器上的任何内容。

如果你的服务器端安全性足够坚固,可以从任何地方攻击你,那么你不必担心EVAL。正如我所提到的,如果EVAL不存在,攻击者有很多工具可以入侵你的服务器,而不管你的浏览器是否具有EVAL功能。

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

"FirstName + ' ' + LastName"

相对于

"LastName + ' ' + FirstName"

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

你可以使用函数代替 eval - function (first, last) { return last + ' ' + first } - Konrad Borowski
列的名称来自数据库。 - Akash Kava
4
eval 的威胁主要来自于其他用户。假设您有一个设置页面,可以让您设置如何向他人显示您的名称。也假设当您编写该页面时没有考虑得很清楚,因此您的选择框中有像<option value="LastName + ' ' + FirstName">Last First</option>这样的选项。我打开我的开发工具,更改一个选项的“值”为alert('PWNED!'),选择更改后的选项并提交表单。 现在,任何时候其他人可以看到我的显示名称,这段代码都会运行。 - cHao
2
@cHao,你所谈论的是服务器安全性差的例子,服务器不应该接受任何可以在任何人的浏览器中执行为代码的数据。再次强调,你没有理解服务器安全性差的概念。 - Akash Kava
@RuudHelderman 能够访问数据库中一条记录的人比 EVAL 更容易受到安全威胁。 - Akash Kava
显示剩余3条评论

6
在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()函数。这完全取决于函数的名称。因此,当使用别名(比如说var noval = eval;),调用本地的eval()函数,然后在内部函数中使用noval(expression)时,如果表达式引用了应该是闭包的变量但实际上并不是,则表达式的求值可能会失败。

5

我看到有人主张不使用eval,因为它是危险的,但是我看到同样的人动态地使用Function和setTimeout,所以他们在幕后使用eval :D

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

现在这篇文章的结尾是:

如果你真的需要它(80%的情况下eval是不需要的),并且你确信自己在做什么,那么就使用eval(或者更好的Function ;)),闭包和面向对象编程可以覆盖80/90%的情况,其中eval可以用其他类型的逻辑进行替换,其余的是动态生成的代码(例如,如果你正在编写解释器),正如你已经说过的评估JSON(在这里,你可以使用Crockford安全评估 ;))


正如Crockford本人所指出的,当前的Web浏览器已经内置了一个函数JSON.parse - Ruud Helderman

3

唯一需要使用eval()的情况是当您需要动态运行JS时。我说的是从服务器异步下载的JS...

...而且有9次中的9次,通过重构代码很容易避免这种情况。


现在,有其他(更好的)从服务器异步加载JavaScript的方法:https://w3bits.com/async-javascript - Ruud Helderman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接