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

Ric*_*ner 253 javascript coding-style eval

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

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

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

小智 254

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

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

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

On to your specific case. From what I understand, you're generating the strings yourself, so assuming you're careful not to allow a string like "rm -rf something-important" to be generated, there's no code injection risk (but please remember, it's very very hard to ensure this in the general case). Also, if you're running in the browser then code injection is a pretty minor risk, I believe.

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

  • 您没有解决使用eval难以调试的代码问题 (74认同)
  • 如果数据来自您的服务器以及您(开发人员)生成的内容,则使用eval()没有任何害处.真正的伤害正在贬低你所阅读的一切.你看到很多人说eval()是邪恶的,他们不知道为什么除了他们在某处读它. (65认同)
  • 如果您对用户的数据非常关注,则代码注入是javascript的一个非常严重的问题.注入的代码将(在浏览器中)运行,就像它来自您的网站一样,让它可以执行用户可以手动执行的任何类型的shenanigan.如果您允许(第三方)代码进入您的页面,它可以代表您的客户订购商品,或者更改他们的图片,或者他们可以通过您的网站做的任何事情.要非常小心.让黑客拥有你的客户就像让他们拥有你的服务器一样糟糕. (46认同)
  • @Sean McMillan:我想相信你,但是如果有人要拦截你的服务器并将javascript转换为`eval()`,他们也可以首先改变页面的来源,并控制用户的信息...我没有看到差异. (40认同)
  • 重新"代码注入 - 再次,浏览器中的JavaScript没有这个问题","而且,如果你在浏览器中运行,那么代码注入是一个相当小的风险,我相信." 您是否建议在浏览器中注入代码不是问题?多年来,XSS一直是OWASP十大榜单中的前三名. (20认同)
  • "浏览器中的JavaScript没有这个问题,因为程序无论如何都在用户自己的帐户中运行" - 这并不能完全解决使用eval的问题.如果一个用户输入的脚本是另一个用户的eval(),则会造成严重破坏.答案似乎并不关心这一点. (9认同)
  • @MikeSamuel,XSS只有在你可以将代码注入_someone else的browser_时才有效("跨站点脚本(XSS)是一种计算机安全漏洞......它使攻击者能够将客户端脚本注入其他用户查看的网页中." [维基百科(http://en.wikipedia.org/wiki/Cross-site_scripting)).因此,如果您评估用户在同一页面上输入的代码,没有任何伤害,没有犯规.如果你允许访客留下未经授权的评论,你就会打开他们留下杜鹃蛋的大门. (6认同)
  • @thirdender,是什么让你相信到达'eval`的字符串不包含由另一个用户控制的字符?原始问题从未声明电子表格只能由一个用户查看. (3认同)
  • @MikeSamuel,每种情况都需要单独处理。这就是为什么我说清理用户输入很重要。不过,就其本身而言,我认为“eval 是邪恶的”是不正确的。然而,如果你发现自己处于一种你认为 `eval` 是你唯一解决方案的情况,你应该停下来想一想。如果可以避免,最好这样做,但不是不惜一切代价。此外,值得注意的是 Douglas Crockford(以“eval is evil”着称)谴责无块语句,并且 `++`/`--` 操作数仅在 `eval` 之后两页([Javascript: The Good Parts](http ://shop.oreilly.com/product/9780596517748.do))。 (2认同)
  • 代码注入:涉及的风险金额取决于eval函数的使用方式.`eval("alert('hello')")`没有比'alert('hello')更多的风险,因为在这两种情况下攻击者都需要改变源来改变任何东西.但是,如果像eval这样的东西("users [""+ username +"']")可能会非常危险,因为攻击者只需要设计一个聪明的用户名,他们就可以代表其他用户运行代码.tl;博士`eval`并不邪恶.`eval` +糟糕的节目是邪恶的. (2认同)

Sho*_*og9 72

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

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

  • 其中一个目的可能是生成优化的代码,这些代码要么太长也不能重复,无法手动编写.LISP中的那种东西会调用宏. (5认同)
  • 更快、更简单、更清晰……这个答案没有很好地涵盖安全隐患。 (5认同)
  • 这是一般性的建议,它可以应用于字面上存在的任何代码块.它真的没有为这个问题添加任何东西; 特别是,来到这里的任何人都无法确定他们的特定用途是否有问题. (4认同)

Tom*_*lak 55

当你信任来源时.

在JSON的情况下,它或多或少难以篡改源,因为它来自您控制的Web服务器.只要JSON本身不包含用户上传的数据,使用eval就没有主要缺点.

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

  • 在eval()中使用之前,应始终针对json语法测试json字符串.所以json字符串"{foo:alert('XSS')}"不会通过,因为"alert('XSS')"不是一个合适的值. (12认同)
  • `eval`也无法正确解析所有有效的JSON字符串.例如``JSON.parse('"\ u2028"')==="\ u2028"`但是`eval('"\ u2028"')引发异常,因为U + 2028是JavaScript中的换行符,但它不是就JSON而言是一个换行符. (7认同)
  • 好吧,然后使用HTTPS.OTOH:中间人不是花园种类网络应用程序的典型攻击场景,而跨站点脚本则是. (3认同)
  • @Justin - 如果协议受到损害,那么,通常初始页面加载将通过相同的协议发送,然后这是一个没有实际意义的问题,因为客户端已经受到了尽可能多的损害。 (2认同)

plo*_*der 23

让我们真正的人:

  1. 现在每个主流浏览器都有一个内置的控制台,你可能会被黑客大量使用来调用任何有价值的函数 - 为什么他们会费心去使用eval语句 - 即使它们可以?

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

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

eval是Evil,eval函数是JavaScript最被滥用的功能.躲开它

正如克罗克福德本人可能会说的那样"这种说法往往会产生非理性的神经症.不要买它."

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

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

  • 重新"每个主要的浏览器现在都有一个内置的控制台......".当一个用户可以输入然后在另一个用户的浏览器中运行的代码时,代码注入是一个问题.浏览器控制台本身不允许一个用户在另一个用户浏览器中运行代码,因此在决定是否值得防止代码注入时它们是无关紧要的. (27认同)
  • "现在每个主流浏览器都有一个内置的控制台......为什么他们会费心去使用eval语句?" - 你离开了标记.我建议你编辑答案.一个用户注入可以在另一个浏览器中运行的代码的能力是一个主要问题.这就是你需要变得真实的地方. (25认同)
  • JQuery使用浏览器的内置JSON.parse函数(如果可用的话更快更安全),仅使用eval作为回退机制."eval is evil"这句话是一个相当好的指导方针. (9认同)
  • @AkashKava你没有意识到的是,如果我在我的评论框中提交javascript,那javascript就会进入数据库.当另一个用户查看该注释(我将javascript放入其中)时,eval会在呈现时使用该javascript,并使用解释器对其进行评估,从而导致我的嵌入式javascript在其他用户的浏览器上执行.通过这样做,我可以收集各种信息.他们的用户名,他们在数据库中的用户ID,他们的电子邮件地址等.这不是一个难的答案,如果你有谷歌搜索XSS,你会在大约10秒内看到为什么这是一个问题. (7认同)
  • @akkishore,如果你想出一个支持你陈述的陈述的真实例子,我将不胜感激. (5认同)
  • 这个答案忽略了跨站点脚本的整个概念。 (4认同)
  • 无论如何,谁是克罗克福德?你是对的,攻击者可以使用任何HTTP浸出工具来模仿浏览器并做任何事情,他不需要EVAL. (3认同)
  • @akkishore,这与EVAL无关,这意味着你没有保护你的DNS或代理或其他什么,记住更多的浏览器,攻击者有很多工具可以伤害除EVAL以外的东西,写这样的话并不能证明什么,给我看看工作代码,这在EVAL中是邪恶的,在任何安全的DNS上执行.如果浏览器或客户端的机器受到损害,EVAL仍然不是邪恶的. (3认同)
  • @AkashKava,服务器安全很重要,但它不是故事的唯一部分.另外,我们不是在谈论在另一个域的上下文中运行代码,我们讨论的是将HTML注入到同一域*上的另一个用户的浏览器*中.例如,如果您运行一个允许人们使用HTML子集发布评论的网站,您必须非常小心,不要像这样允许HTML:`<div onclick ="恶意代码">`.这并不简单.如果你允许HTML在另一个用户的浏览器中运行,那么攻击者可以做坏事,正如我在上一篇评论中提到的那样. (3认同)
  • @AkashKava,我想这里的其他人都在暗示它,但是评估确实是一个问题。跨站点脚本(XSS)攻击是攻击者导致JS代码在另一个用户浏览器的上下文中运行的地方。您可能会想,他们可以做什么?他们可以窃取您的会话并在该站点上冒充您。他们可以发布垃圾评论。他们可以收集您的个人信息。想象一下,这是在银行网站上发生的:他们可以窃取您的帐号和余额。我建议您阅读https://en.wikipedia.org/wiki/Cross-site_scripting。 (2认同)
  • 这个答案非常错误,我很震惊它竟然得到了任何赞成票。评估 JSON?你疯了?听说过解析器或代码注入吗? (2认同)
  • @AkashKava我不知道为什么我什至要以回应来端庄,但我是。字符串中的Java脚本本身并没有害处,但是使用eval会使javascript运行,而否则它将被解释为字符串,而不会被解析。我不同意应该进行服务器端转义,但是,使用eval只是向某人寻求一种利用该领域的方法。无论您如何争论,它都确实会带来安全风险,并且是一种不良做法。期。这就是为什么这里的每个人都在回应我关于不使用eval的说法。 (2认同)

swi*_*ams 18

我倾向于遵循克罗克福德的意见eval(),并完全避免.即使看起来需要它的方式也没有.例如,setTimeout()允许您传递函数而不是eval.

setTimeout(function() {
  alert('hi');
}, 1000);
Run Code Online (Sandbox Code Playgroud)

即使它是一个值得信赖的来源,我也不会使用它,因为JSON返回的代码可能会出现乱码,最多可能会做一些不好的事情,最坏的情况是暴露出坏事.

  • 如果某人可以执行中间人攻击,他可以轻松地向您的脚本注入任何内容. (11认同)
  • 你根本不应该依赖你的javascript代码......你不要依赖在客户端运行的任何东西......如果有人做了中间人攻击,为什么他会搞乱你的json对象?他可以为您和不同的js文件提供不同的网页... (10认同)
  • 我个人不喜欢"总有其他方法可以做到这一点".例如,您还可以说总是有办法避免面向对象的编程.这并不意味着它不是一个很好的选择.如果您了解eval及其危险性,它可以成为在正确情况下使用的好工具. (4认同)
  • 如果您的网络服务器未通过HTTPS进行身份验证,那么您可能遭受某种中间人攻击,其中另一台主机拦截请求并发送自己的数据. (3认同)
  • 我认为服务器端的JSON格式化程序中的错误肯定是个问题.服务器的响应是否依赖于任何类型的用户提交的文本?然后你得关注XSS. (2认同)

Ste*_*gin 8

底线

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

稍微详细一点

eval如果在服务器上运行时使用客户端提交的输入(该输入不是由开发人员创建未经开发人员清理) ,则为邪恶

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

显然,您应该始终清理输入,以便对代码消耗的内容进行一些控制。

推理

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

  • 很显然这不是真的。否则 XSS 就不会成为安全漏洞。我真正想说的是,“eval”与设置“innerHTML”一样都是一个安全漏洞。只要您知道自己在做什么就没有问题,但如果您不小心,可能会受到一些攻击(即窃取 cookie)。 (2认同)

Aka*_*ava 6

Eval 是对用于代码模板化的编译的补充。我所说的模板化是指您编写一个简化的模板生成器,它可以生成有用的模板代码,从而提高开发速度。

我写了一个框架,其中开发人员不使用 EVAL,但他们使用我们的框架,而该框架必须使用 EVAL 来生成模板。

使用以下方法可以提高 EVAL 的性能;您必须返回一个函数,而不是执行脚本。

var a = eval("3 + 5");
Run Code Online (Sandbox Code Playgroud)

它应该被组织为

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

var a = f(3,5);
Run Code Online (Sandbox Code Playgroud)

缓存 f 肯定会提高速度。

Chrome 还允许非常轻松地调试此类功能。

关于安全性,使用 eval 与否几乎没有任何区别,

  1. 首先,浏览器在沙箱中调用整个脚本。
  2. 任何在 EVAL 中是邪恶的代码,在浏览器本身中都是邪恶的。攻击者或任何人都可以轻松地在 DOM 中注入一个脚本节点,如果他/她可以评估任何东西,他/她就可以做任何事情。不使用 EVAL 不会有任何区别。
  3. 有害的主要是服务器端安全性差。服务器上糟糕的 cookie 验证或糟糕的 ACL 实现会导致大多数攻击。
  4. 最近的 Java 漏洞等存在于 Java 的本机代码中。JavaScript 过去并且旨在在沙箱中运行,而小程序则被设计为在沙箱外运行,并带有证书等,这会导致漏洞和许多其他事情。
  5. 编写模仿浏览器的代码并不难。您所要做的就是使用您最喜欢的用户代理字符串向服务器发出 HTTP 请求。无论如何,所有测试工具都模拟浏览器;如果攻击者想要伤害您,EVAL 是他们的最后手段。他们有许多其他方法来处理您的服务器端安全问题。
  6. 浏览器 DOM 无权访问文件和用户名。事实上,机器上没有任何 eval 可以访问的内容。

如果您的服务器端安全性足以让任何人从任何地方进行攻击,您就不必担心 EVAL。正如我所提到的,如果 EVAL 不存在,攻击者有许多工具可以入侵您的服务器,而不管您的浏览器的 EVAL 功能如何。

eval 只适用于生成一些模板,根据一些事先没有使用过的东西来做复杂的字符串处理。例如,我会更喜欢

"FirstName + ' ' + LastName"
Run Code Online (Sandbox Code Playgroud)

"LastName + ' ' + FirstName"
Run Code Online (Sandbox Code Playgroud)

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

  • `eval` 的威胁主要是*其他用户*。假设您有一个设置页面,它可以让您设置您的名字对其他人的显示方式。假设您在编写它时并没有想得很清楚,因此您的选择框具有诸如`&lt;option value="LastName + ' ' + FirstName"&gt;Last First&lt;/option&gt;` 之类的选项。我打开我的开发工具,将选项的 `value` 更改为 `alert('PWNED!')`,选择更改后的选项,然后提交表单。现在,只要其他人可以看到我的显示名称,该代码就会运行。 (3认同)
  • @cHao,您所谈论的是服务器端安全性较差的示例,服务器永远不应该接受可以在任何人的浏览器中作为代码执行的数据。再一次,您未能理解服务器端安全性差的概念。 (2认同)

ken*_*ura 5

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

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

现在这篇文章的结语是:

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


Ben*_*min 5

在 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();
})();
Run Code Online (Sandbox Code Playgroud)

我想在这里指出的是, eval() 不一定是指本机eval()函数。这一切都取决于函数的名称。因此,当eval()使用别名调用本机时(比如var noval = eval;然后在内部函数中noval(expression);),expression当它引用应该是闭包一部分的变量时,评估可能会失败,但实际上不是。