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

时间:2008-09-17 19:09:55

标签: javascript security eval

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

25 个答案:

答案 0 :(得分:368)

  1. eval 的不当使用会打开您的网站 注射攻击代码

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

  3. 评估代码执行速度较慢(无法编译/缓存评估代码)

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

答案 1 :(得分:343)

eval并不总是邪恶的。有时候这是完全合适的。

然而,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元素的狡猾的旧技巧更可靠。)

答案 2 :(得分:36)

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

答案 3 :(得分:26)

有两点想到:

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

  2. 性能:在要执行的代码未知之前,无法对其进行优化。 (关于javascript和性能,当然Steve Yegge's presentation

答案 4 :(得分:20)

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

答案 5 :(得分:17)

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

答案 6 :(得分:15)

主要是,维护和调试要困难得多。这就像goto。你可以使用它,但它会让那些可能需要稍后进行更改的人更难找到问题并且更加困难。

答案 7 :(得分:13)

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

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

因此,如果您希望运行一些JavaScript代码,否则可能会被允许(Myspace,我正在看着您......)然后eval()可能是一个有用的技巧。

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

答案 8 :(得分:11)

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

答案 9 :(得分:7)

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

答案 10 :(得分:6)

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

虽然这非常有用,但是在适度使用时可以添加很多好的功能。

答案 11 :(得分:5)

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

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

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

答案 12 :(得分:5)

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

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

答案 13 :(得分:5)

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

答案 14 :(得分:4)

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

答案 15 :(得分:4)

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

答案 16 :(得分:2)

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

答案 17 :(得分:2)

如果您正在执行用户提交的代码,除了可能存在的安全问题之外,大多数时候还有一种更好的方法,即每次执行代码时都不需要重新解析代码。匿名函数或对象属性可以替代eval的大多数用法,并且更安全,更快。

答案 18 :(得分:2)

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

  

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

答案 19 :(得分:2)

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

答案 20 :(得分:1)

我不会试图反驳此前所说的任何内容,但我会提供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);
编辑:顺便说一句,我不建议(出于前面指出的所有安全原因)你在用户输入上建立对象名称。我无法想象你有什么理由想要这样做。不过,我想我会指出这不是一个好主意:)。

答案 21 :(得分:1)

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

答案 22 :(得分:1)

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

答案 23 :(得分:1)

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

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

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

如果你问是否适合在PHP中使用eval(),答案是,除非你白名单任何可能传递的值你的评估声明。

答案 24 :(得分:0)

垃圾收集

浏览器的垃圾回收不知道是否可以将已评估的代码从内存中删除,因此仅将其保存直到重新加载页面为止。 如果您的用户不久才出现在您的页面上,也不错,但这对于webapp来说可能是个问题。

这是演示问题的脚本

https://jsfiddle.net/CynderRnAsh/qux1osnw/

document.getElementById("evalLeak").onclick = (e) => {
  for(let x = 0; x < 100; x++) {
    eval(x.toString());
  }
};

与上面的代码一样简单的操作会导致少量内存被存储,直到应用程序终止。 当逃避的脚本是一个巨大的函数并按间隔调用时,情况更糟。