it-swarm.cn

为什么使用JavaScript eval函数是一个坏主意?

Eval函数是一种动态生成代码的强大而简单的方法,那么有什么警告呢?

502
Brian Singh
  1. 不正确使用 eval 打开注射攻击代码

  2. 调试 可能更具挑战性(没有行号等)

  3. eval'd代码执行速度较慢(无法编译/缓存eval'd代码)

编辑:正如@Jeff Walden在评论中指出的那样,#3今天不像2008年那么真实。但是,虽然编译脚本的某些缓存可能会发生,但这仅限于重复进行重复而无需修改的脚本。更可能的情况是,您正在评估每次都经过轻微修改的脚本,因此无法缓存。我们只说一些eval'd代码执行得更慢。

367
Prestaul

评估并不总是邪恶的。有时候它非常合适。

然而,eval目前和历史上大量过度使用,他们不知道自己在做什么。不幸的是,这包括编写JavaScript教程的人,在某些情况下,这确实会带来安全后果 - 或者更常见的是简单的错误。因此,我们越多可以在eval上抛出问号,就越好。每当你使用eval时,你需要理智地检查你正在做什么,因为你可能会做得更好,更安全,更清洁。

举一个非常典型的例子,设置一个id存储在变量'potato'中的元素的颜色:

eval('document.' + potato + '.style.color = "red"');

如果上面那种代码的作者对JavaScript对象的工作原理有了线索,他们就会意识到可以使用方括号而不是文字点名,从而避免了对eval的需求:

document[potato].style.color = 'red';

...这更容易阅读,也不那么潜在的错误。

(但是,有人/他/他/他们知道他们在做什么会说:

document.getElementById(potato).style.color = 'red';

这比直接从文档对象访问DOM元素的狡猾的旧技巧更可靠。)

342
bobince

我相信这是因为它可以从字符串中执行任何JavaScript函数。使用它可以让人们更容易将恶意代码注入应用程序。

36
kemiller2002

我想到两点:

  1. 安全性(但只要您自己生成要评估的字符串,这可能不是问题)

  2. 性能:直到要执行的代码未知,它无法进行优化。 (关于javascript和性能,当然 Steve Yegge的演讲

26
xtofl

将用户输入传递给eval()存在安全风险,但每次调用eval()都会创建JavaScript解释器的新实例。这可能是资源匮乏。

20
Andrew Hedges

如果您传递eval用户输入,通常只会出现问题。

17
Mark Biek

主要是,维护和调试要困难得多。这就像goto。您可以使用它,但这会使发​​现问题变得更加困难,并且对于可能需要稍后进行更改的人员更加困难。

15
Brian

要记住的一件事是,您通常可以使用eval()在受限制的环境中执行代码 - 阻止特定JavaScript函数的社交网站有时会被错误地在eval块中分解 -

eval('al' + 'er' + 't(\'' + 'hi there!' + '\')');

因此,如果你想要运行一些JavaScript代码而不允许它( Myspace ,我正在看着你......)那么eval()可能是一个有用的技巧。

但是,由于上面提到的所有原因,你不应该将它用于你自己的代码,你可以完全控制它 - 它只是没有必要,而且更好地降级到'棘手的JavaScript黑客'架子。

13
matt lohkamp

除非您让eval()成为动态内容(通过cgi或输入),否则它与您页面中的所有其他JavaScript一样安全可靠。

11
Thevs

除了其他答案之外,我认为eval语句不能具有高级最小化。

7
Paul Mendoza

这是一种可能的安全风险,它具有不同的执行范围,并且效率非常低,因为它为代码的执行创建了一个全新的脚本环境。有关更多信息,请参阅此处: eval

但它非常有用,并且适度使用可以添加许多好的功能。

6
Tom

我知道这个讨论已经过时了,但我真的很喜欢 这个 谷歌的做法,并希望与他人分享这种感觉;)

另一件事就是越多越好你得到越多你试图理解,最后你只是因为有人这么说而不相信某事好坏,这是一个非常鼓舞人心的 视频 帮助我我自己多思考:)好的做法很好,但不要盲目地使用它们:)

5
op1ekun

除非您100%确定所评估的代码来自可信来源(通常是您自己的应用程序),否则这是将您的系统暴露给跨站点脚本攻击的绝对方式。

5
John Topley

如果你知道你正在使用它的上下文,它不一定是那么糟糕。

如果您的应用程序使用eval()从某个JSON创建一个对象,该对象从 XMLHttpRequest 返回到您自己的站点,由您的可信服务器端代码创建,则可能不是问题。

不受信任的客户端JavaScript代码无论如何都无法做到这一点。如果您正在执行eval()的事情来自合理的来源,那么你没事。

5
MarkR

它大大降低了您对安全性的信心。

4
David Plumpton

如果您希望用户输入一些逻辑函数并评估AND OR那么JavaScript eval函数是完美的。我可以接受两个字符串和eval(uate) string1 === string2等。

4
Ian

如果你发现代码中使用了eval(),请记住“eval()是邪恶的”。

此函数接受任意字符串并将其作为JavaScript代码执行。当事先知道相关代码(未在运行时确定)时,没有理由使用eval()。如果代码是在运行时动态生成的,那么通常有一种更好的方法来实现没有eval()的目标。例如,仅使用方括号表示法来访问动态属性更好更简单:

// antipattern
var property = "name";
alert(eval("obj." + property));

// preferred
var property = "name";
alert(obj[property]);

使用eval()也会带来安全隐患,因为您可能正在执行已被篡改的代码(例如来自网络)。在处理来自Ajax请求的JSON响应时,这是一个常见的反模式。在这些情况下,最好使用浏览器的内置方法来解析JSON响应,以确保它安全有效。对于本机不支持JSON.parse()的浏览器,您可以使用JSON.org中的库。

同样重要的是要记住,将字符串传递给setInterval()setTimeout()Function()构造函数在很大程度上类似于使用eval(),因此应该避免。

在幕后,JavaScript仍然必须评估并执行您作为编程代码传递的字符串:

// antipatterns
setTimeout("myFunc()", 1000);
setTimeout("myFunc(1, 2, 3)", 1000);

// preferred
setTimeout(myFunc, 1000);
setTimeout(function () {
myFunc(1, 2, 3);
}, 1000);

使用新的Function()构造函数与eval()类似,应小心处理。它可能是一个强大的构造,但经常被误用。如果绝对必须使用eval(),则可以考虑使用新的Function()。

由于在新的Function()中计算的代码将在本地函数作用域中运行,因此有一个很小的潜在好处,因此在被评估的代码中使用var定义的任何变量都不会自动成为全局变量。

防止自动全局变量的另一种方法是将eval()调用包装成立即函数。

3
12345678

除了可能的安全问题,如果您正在执行用户提交的代码,大多数情况下,有一种更好的方法,不涉及每次执行时重新解析代码。匿名函数或对象属性可以替换eval的大多数用法,并且更安全,更快速。

2
Matthew Crumley

这可能会成为一个问题,因为下一代浏览器出现了一些JavaScript编译器。通过Eval执行的代码可能无法针对这些较新的浏览器执行其他JavaScript。有人应该做一些分析。

2
Brian

eval()非常强大,可用于执行JS语句或计算表达式。但问题不在于eval()的使用,而是让我们说一下你用eval()运行的字符串如何受到恶意方的影响。最后,您将运行恶意代码。随着权力的来临,责任重大所以明智地使用它是你正在使用它。这与eval()函数无关,但是这篇文章有很好的信息: http://blogs.popart.com/2009/07/javascript-injection-attacks/ 如果你正在寻找基础知识eval()看看这里: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

2
Phi

这是一篇关于eval以及它如何不是邪恶的好文章之一: http://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/

我不是说你应该跑出去并开始在各地使用eval()。事实上,运行eval()的好用例很少。代码清晰度,可调试性以及当然不应忽视的性能肯定存在问题。但是当你遇到eval()有意义的情况时,你不应该害怕使用它。尽量不要先使用它,但是当适当使用eval()时,不要让任何人吓到你认为你的代码更脆弱或更不安全。

2
Amr Elgarhy

这并不总是一个坏主意。以代码生成为例。我最近写了一个名为 Hyperbars 的库,它弥合了 virtual-domhandlebars 之间的差距。它通过解析把手模板并将其转换为 hyperscript 来实现这一点,后者由virtual-dom使用。 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 ,你可以看到代码生成是如何实现的。

1
Wikened

我不会试图反驳此前所说的任何内容,但我会提供eval()的使用(据我所知)不能以任何其他方式完成。可能还有其他方法可以对此进行编码,也可能是对其进行优化的方法,但这样做是为了清晰起见,并且没有任何花哨和口哨,以说明使用eval,实际上没有任何其他选择。即:动态(或更准确地)编程创建的对象名称(与值相对)。

//Place this in a common/global JS lib:
var NS = function(namespace){
    var namespaceParts = String(namespace).split(".");
    var namespaceToTest = "";
    for(var i = 0; i < namespaceParts.length; i++){
        if(i === 0){
            namespaceToTest = namespaceParts[i];
        }
        else{
            namespaceToTest = namespaceToTest + "." + namespaceParts[i];
        }

        if(eval('typeof ' + namespaceToTest) === "undefined"){
            eval(namespaceToTest + ' = {}');
        }
    }
    return eval(namespace);
}


//Then, use this in your class definition libs:
NS('Root.Namespace').Class = function(settings){
  //Class constructor code here
}
//some generic method:
Root.Namespace.Class.prototype.Method = function(args){
    //Code goes here
    //this.MyOtherMethod("foo"));  // => "foo"
    return true;
}


//Then, in your applications, use this to instantiate an instance of your class:
var anInstanceOfClass = new Root.Namespace.Class(settings);

编辑:顺便说一下,我不建议(出于前面指出的所有安全原因)你在用户输入上建立对象名称。我无法想象你有什么理由想要这样做。不过,我想我会指出它不是一个好主意:)

1
Carnix

我甚至会说,如果你在浏览器中运行的javascript中使用eval()并不重要。*(警告)

所有现代浏览器都有一个开发人员控制台,您可以在其中执行任意javascript,任何半智能开发人员都可以查看您的JS源代码并将他们需要的任何内容放入开发控制台中以执行他们想要的操作。

*只要您的服务器端点具有用户提供的值的正确验证和清除,在客户端javascript中解析和评估的内容应该无关紧要。

如果您在PHP中询问是否适合使用eval(),则答案为 _ no _ ,除非您 白名单 可以传递给您的eval语句的任何值。

1
Adam Copley

JavaScript Engine在编译阶段执行了许多性能优化。其中一些归结为能够基本上静态地分析代码,并预先确定所有变量和函数声明的位置,以便在执行期间解析标识符所需的工作量更少。

但是如果引擎在代码中找到了一个eval(..),它基本上必须假设它对标识符位置的所有意识都可能是无效的,因为它无法在最近的时间知道你可以传递给eval(...)的代码。修改词法范围,或者您可以传递给的对象的内容,以创建一个新的词法范围进行查阅。

换句话说,在悲观意义上,如果存在eval(..),那么它所做的大多数优化都是没有意义的,因此根本不执行优化。

这解释了一切。

参考:

https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20&%20closures/ch2.md#eval

https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20&%20closures/ch2.md#performance

1
hkasera