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


534

eval函数是动态生成代码的强大而简便的方法,那么有哪些警告?


92
不要做eval()by Simon Willison-24ways.org/2005/dont-be-eval
Brian Singh

5
正如moduscreate.com/javascript-performance-tips-tricks中概述的那样 -(new Function(str))()比eval(str)性能更高。只是我的2美分:)
Grgur

3
显然,新功能(a)比铬的eval(a)慢67%
什么是睡眠

对我来说,新功能(a)在osx上的最新Chrome浏览器的速度要慢80%
约翰·史密斯

3
我添加了一个静态函数,只是为了比较性能。jsperf.com/eval-vs-new-function/2
Nepoxx,2014年

Answers:


386
  1. 错误使用eval会使您的代码遭受注入攻击

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

  3. 评估的代码执行速度较慢(没有机会编译/缓存评估的代码)

编辑:正如@Jeff Walden在评论中指出的那样,今天#3的真实性要低于2008年。但是,尽管可能会缓存一些已编译脚本,但这仅限于未经修改就重复的脚本。更有可能的情况是您正在评估每次都经过略微修改且因此无法缓存的脚本。我们只说某些评估代码的执行速度较慢。


2
@JeffWalden,很好的评论。我已经更新了我的帖子,尽管我知道距您发布已经一年了。Xnzo72,如果您对评论有所保留(如Jeff所做的那样),那么我也许可以同意您的看法。Jeff指出了关键:“多次评估相同的字符串可以避免解析开销”。实际上,您只是错了。#3在许多情况下都适用。
Prestaul

7
@Prestaul:既然假定的攻击者可以使用任何开发人员工具来更改客户端中的JavaScript,那么为什么说Eval()对注入攻击开放代码?还没开吗?(我当然是在谈论客户端JavaScript)
Eduardo Molteni 2012年

60
@EduardoMolteni,我们不在乎(实际上也不能阻止)用户在自己的浏览器中执行js。我们试图避免的攻击是保存用户提供的值,然后将其放入javascript中并进行评估。例如,我可以将用户名设置为:badHackerGuy'); doMaliciousThings();如果使用我的用户名,将其连接到某个脚本中并在其他人的浏览器中进行评估,那么我可以在他们的计算机上运行我想要的任何javascript(例如,强制他们为我的帖子+1,将其数据发布到我的服务器等)
Prestaul,2012年

3
通常,如果不是大多数函数调用,则#1在很多情况下都是正确的。eval()不应被经验丰富的程序员挑出来并避免,因为没有经验的程序员会滥用它。但是,经验丰富的程序员通常在其代码中拥有更好的体系结构,并且由于这种更好的体系结构,很少需要甚至不考虑eval()。
frodeborli 2014年

4
@TamilVendhan确保您可以设置断点。您可以通过将debugger;语句添加到源代码中来访问由Chrome创建的虚拟文件,以用于您的无效代码。这将停止在该行上执行程序。然后,您可以像其他JS文件一样添加调试断点。
2014年

346

评估并不总是邪恶的。有时它是完全合适的。

但是,eval在当前和历史上被不知道自己在做什么的人过度使用。不幸的是,这包括编写JavaScript教程的人员,在某些情况下,这的确可能带来安全后果-或更常见的是简单的错误。因此,我们可以做的越多,就对eval提出问号越好。每次使用eval时,您都需要检查自己在做什么,因为您可能会以更好,更安全,更干净的方式进行操作。

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

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

如果上面的代码类型的作者对JavaScript对象的工作原理有一定的了解,他们将意识到可以使用方括号代替文字点名,从而避免了eval的需要:

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

...这更容易阅读,而且不存在潜在的错误。

(但是然后,一个/ really /知道自己在做什么的人会说:

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

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


82
嗯,猜想我第一次学习JavaScript的时候很幸运。我总是使用“ document.getElementById”来访问DOM。具有讽刺意味的是,我当时只是这么做是因为我不知道对象如何在JavaScript中工作;-)
Mike Spross 2009年

5
同意。有时eval还可以,例如,来自Web服务的JSON响应
schoetbi 2011年

40
@schoetbi:你不应该使用JSON.parse(),而不是eval()用于JSON?
nyuszika7h 2011年

4
@bobince code.google.com/p/json-sans-eval可在所有浏览器上使用,github.com/douglascrockford/JSON-js也是如此。道格·克罗克福德(Doug Crockford)的json2.js确实在内部使用eval,但带有检查功能。此外,它与内置浏览器对JSON的支持向前兼容。
Martijn

8
@bobince有一种称为功能检测和polyfill的东西来处理缺少的JSON库和其他东西(请
参见

37

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


5
那有什么选择呢?
现代人

5
确实,替代方法是只编写不需要的代码。Crockford对此进行了详尽的介绍,如果您需要使用它,他几乎说这是程序设计的缺陷,需要重新设计。实际上,我也同意他的看法。JS的所有缺点都是非常灵活的,并且有很大的空间使其灵活。
kemiller2002

3
并非如此,大多数框架都有解析JSON的方法,如果您不使用框架,则可以使用JSON.parse()。大多数浏览器都支持它,如果您真的很困难,则可以很轻松地为JSON编写解析器。
kemiller2002

6
我不赞成这种说法,因为将恶意代码注入Javascript应用程序已经很容易了。我们有浏览器控制台,脚本扩展等。发送给客户端的每段代码对于客户端来说都是可选的。
user2867288'2

6
关键是我可以更轻松地将代码注入您的浏览器。假设您在查询字符串上使用eval。如果我诱骗您单击带有附加查询字符串的指向该站点的链接,那么我现在已经在浏览器的完全许可下在您的计算机上执行了代码。我想记录您在该站点上键入的所有内容并发送给我吗?做完了,没有办法阻止我,因为执行eval时,浏览器会赋予它最高权限。
kemiller2002'2

27

我想到两点:

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

  2. 性能:除非要执行的代码未知,否则无法对其进行优化。(关于javascript和性能,当然是Steve Yegge的介绍


9
如果客户无论如何都可以用我们的代码处理他/她想要的东西,为什么会有安全问题?Greasemonkey?
Paul Brewczynski 2013年

2
@PaulBrewczynski,当用户A保存要使用的代码部分,eval然后在用户B的浏览器上运行该小段代码时,就会出现安全问题
Felipe Pereira 2014年



16

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


Eval可用于替代缺少的元编程功能,例如模板。我喜欢紧凑的生成器方式,而不是功能上无穷无尽的功能列表。
弱点

只要字符串不是来自用户的,或者仅限于浏览器即可。JavaScript具有使用诸如更改原型,obj [member],Proxy,json.parse,window,装饰器函数(副词)之类的东西的元编程功能,其中newf =装饰器(oldf),高​​阶函数(例如Array.prototype.map(f)) ,将参数传递给其他函数,并通过{}传递关键字参数。您能告诉我一个用例,您不能做这些事而不是评估吗?
aoeu256 '19

13

要记住的一件事是,您经常可以在其他受限制的环境中使用eval()执行代码-阻止特定JavaScript功能的社交网站有时可能会通过在eval块中将它们分解而被愚弄-

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

因此,如果您正在寻找一些可能无法通过其他方式运行的JavaScript代码(Myspace,我在看着您...),那么eval()可能是一个有用的技巧。

但是,由于上述所有原因,您不应该将其用于拥有完全控制权的自己的代码中-只是没有必要,而且最好将其降级为“棘手的JavaScript骇客”工具。


1
只需更新上面的代码即可。--hi !!-需要用引号引起来,因为它是一个字符串。eval('al'+'er'+'t('+'“ hi在那里!”'+')');
Mahesh 2014年

2
[]["con"+"struc"+"tor"]["con"+"struc"+"tor"]('al' + 'er' + 't(\'' + 'hi there!' + '\')')()
Konrad Borowski14年

3
kes,有些社交网站限制了alert()但允许了eval()?!
joshden '16

12

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


确实是这样-如果您的内容不是动态的,那么到底有什么原因要使用eval?您可以将代码放在函数中并调用它!
Periata Breatta

仅举例来说-解析来自Ajax调用的返回值(JSON,例如服务器定义的字符串等)。
Thevs

2
哦,我明白了。我之所以称其为动态客户,是因为客户不知道它们是什么,但是我现在明白了您的意思。
Periata Breatta


6

这可能会带来安全风险,执行范围不同,效率很低,因为它会为代码执行创建全新的脚本环境。有关更多信息,请参见此处:eval

但是,它非常有用,与审核一起使用可以添加许多良好的功能。


5

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


1
仅当您的服务器端安全性很差时。客户端安全是毫无意义的。
doubleOrt

5

我知道这个讨论很古老,但是我真的很喜欢Google的这种方法,并希望与他人分享这种感觉;)

另一件事是,您越了解越多,您就会尝试理解,最后您只是不相信某事是好是坏,只是因为有人这么说:)这是一部非常鼓舞人心的视频,帮助我自己思考了更多:)好的做法很好,但是请不要轻率地使用它们:)


1
是。到目前为止,至少在Chrome中,我发现一个用例似乎更有效。运行一个RAF,该RAF每次迭代都评估给定变量中的字符串。使用setInterval只能在该字符串中设置面向图形的内容,例如设置转换等。我尝试了许多其他方法,例如遍历数组中的函数或使用变量仅对已更改的元素设置转换,但是到目前为止,这似乎是最有效的。当涉及到动画时,最真实的基准测试就是您的眼睛。
jdmayfield '18 -2-8

5

只要您知道使用它的上下文,并不一定很糟。

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

无论如何,不​​受信任的客户端JavaScript代码不能做太多事情。只要您执行的事情eval()来自合理的来源,就可以了。


3
使用eval不仅比解析JSON还慢吗?
布伦丹·朗

@Qix-在我的浏览器(Chrome 53)上运行该测试显示,evalparse快一些。
Periata Breatta '16

@PeriataBreatta呵呵,很奇怪。我想知道为什么。当时我评论并非如此。但是,Chrome在不同版本的运行时的某些区域获得奇怪的性能提升并非闻所未闻。
Qix-蒙尼卡(Monica)

这里有一个旧线程,但是从我读过的内容(不是声称我自己追溯了它)来看,JSON.parse实际上是eval的最后输入。因此,从效率角度考虑,需要花费更多的工作/时间。但是出于安全考虑,为什么不仅仅解析呢?评估是一个了不起的工具。用它来处理其他事情。要通过JSON传递函数,有一种方法无需评估即可。JSON.stringify中的第二个参数使您可以运行一个回调,可以通过typeof检查它是否是一个函数。然后获取函数的.toString()。如果您进行搜索,就会有一些很好的文章。
jdmayfield '18 -2-8


4

如果您希望用户输入一些逻辑函数并求与与或,则JavaScript eval函数是理想的选择。我可以接受两个字符串和eval(uate) string1 === string2,等等。


您也可以使用Function(){},但是在服务器上使用它们时要小心,除非您希望用户接管您的服务器hahahah。
aoeu256 '19

3

如果在代码中发现使用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(),则可以考虑使用new Function()代替。

潜在的好处很小,因为在new Function()中评估的代码将在局部函数范围内运行,因此在评估的代码中用var定义的任何变量都不会自动成为全局变量。

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


您能建议我如何评估不带eval的函数局部动态变量名称吗?Eval(及类似)函数在包含它们的大多数语言中是最后一种,但有时是必需的。在获得动态变量名称的情况下,是否有任何解决方案更安全?无论如何,javascript本身都不是真正的安全性(服务器端显然是主要的防御手段)。如果您有兴趣,这是我的eval用例,我很想更改它:stackoverflow.com/a/48294208
常规Joe Joe

2

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


2

随着下一代浏览器以某种JavaScript编译器的形式出现,这可能会成为一个更大的问题。在这些较新的浏览器上,通过Eval执行的代码可能无法像其他JavaScript一样出色地运行。有人应该做一些分析。


2

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

我并不是说您应该精疲力尽,并开始在任何地方使用eval()。实际上,运行eval()的好用例很少。绝对存在代码清晰度,可调试性和性能方面的问题,这些问题不容忽视。但是,当您遇到eval()有意义的情况时,不要害怕使用它。尝试不要先使用它,但是当适当使用eval()时,不要让任何人吓到您认为代码更脆弱或更不安全。


2

eval()非常强大,可用于执行JS语句或评估表达式。但是问题不关乎eval()的使用,而只是说一些您用eval()运行的字符串如何受到恶意方的影响。最后,您将运行恶意代码。权力伴随着巨大的责任。因此,明智地使用它就是您正在使用它。这与eval()函数关系不大,但是本文提供了相当不错的信息:http : //blogs.popart.com/2009/07/javascript-injection-attacks/ 如果您正在寻找eval()的基础知识在这里查看:https : //developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/eval


2

JavaScript引擎在编译阶段执行了许多性能优化。其中一些可以归结为能够在进行词法分析时基本静态地分析代码,并预先确定所有变量和函数声明的位置,以便在执行过程中花费较少的精力来解析标识符。

但是,如果引擎在代码中找到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


没有javascript引擎无法100%保证在代码中找到和评估。因此,它必须随时准备就绪。
杰克·吉芬

2

这并不总是一个坏主意。以代码生成为例。我最近写了一个名为Hyperbars的库,该库弥合了Virtual-domhandlebars之间的鸿沟。它通过解析车把模板并将其转换为超级脚本(随后由虚拟域使用)来实现。超级脚本首先作为字符串生成,然后在将其返回之前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()在这种情况下不是问题,因为您只需解释一次生成的字符串,然后多次重复使用可执行输出。

你可以看到,如果你好奇的代码生成是如何实现的这里


2

我要说的是,如果您使用eval()在浏览器中运行的javascript 并没有关系。*(caveat)

所有现代的浏览器都有一个开发人员控制台,您可以在其中执行任意JavaScript,任何半聪明的开发人员都可以查看您的JS源,并将它们需要的所有内容放入开发控制台中以完成所需的操作。

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

但是,如果您询问是否适合eval()在PHP中使用,答案是“ 否”,除非您将可能传递给eval语句的任何值列入白名单


不仅有开发控制台,还可以在url栏中键入javascript:code,以在页面上构建自己的开发控制台(如果没有的话),就像在旧版IE浏览器和移动设备上一样。
德米特里(Dmitry)

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);

编辑:顺便说一句,出于您之前指出的所有安全原因,我不建议您将对象名称基于用户输入。我无法想象您有什么充分的理由要这么做。不过,我想指出一点,这不是一个好主意:)


3
可以使用来完成操作namespaceToTest[namespaceParts[i]],这里不需要评估,因此if(typeof namespaceToTest[namespaceParts[i]] === 'undefined') { namespaceToTest[namespaceParts[i]] = {};else namespaceToTest = namespaceToTest[namespaceParts[i]];
–user2144406

1

垃圾收集

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

这是演示问题的脚本

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.