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

Why is using the JavaScript eval function a bad idea?

提问人:Brian Singh 提问时间:9/18/2008 最后编辑:Nisarg ShahBrian Singh 更新时间:2/4/2023 访问量:290180

问:

eval 函数是一种强大而简单的动态生成代码的方法,那么需要注意哪些?

JavaScript 安全 评估

评论

100赞 Brian Singh 9/18/2008
Don't be eval() 作者:Simon Willison - 24ways.org/2005/dont-be-eval
6赞 Grgur 8/2/2012
moduscreate.com/javascript-performance-tips-tricks 中所述 - (new Function(str))() 比 eval(str) 性能更高。只有我的 2 美分:)
3赞 what is sleep 12/11/2013
显然,新的 FCUNTION(A) 比 Chrome 上的 Eval(A) 慢 67%
3赞 Nepoxx 8/29/2014
我添加了一个静态函数,只是为了比较性能。jsperf.com/eval-vs-new-function/2
1赞 9pfs 3/20/2022
@Nepoxx 您的网站已关闭

答:

39赞 kemiller2002 9/18/2008 #1

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

评论

7赞 moderns 5/12/2014
那么有什么选择呢?
5赞 kemiller2002 5/12/2014
实际上,另一种选择是编写不需要它的代码。Crockford对此进行了详细的讨论,如果你需要使用它,他几乎说这是一个程序设计缺陷,需要重新设计。事实上,我也同意他的观点。JS 尽管它有缺陷,但它非常灵活,并且允许很大的空间来使其灵活。
3赞 kemiller2002 5/12/2014
事实并非如此,大多数框架都有解析 JSON 的方法,如果您不使用框架,则可以使用 JSON.parse()。大多数浏览器都支持它,如果你真的处于紧要关头,你可以很容易地为 JSON 编写一个解析器。
6赞 user2867288 2/4/2015
我不相信这个论点,因为将流氓代码注入 Javascript 应用程序已经很容易了。我们有浏览器控制台、脚本扩展等......发送给客户端的每一段代码对于客户端执行都是可选的。
7赞 kemiller2002 2/4/2015
关键是,我更容易将代码注入您的浏览器。假设您正在对查询字符串使用 eval。如果我诱骗您单击附加了我的查询字符串的指向该站点的链接,那么我现在已经在浏览器的完全许可下在您的计算机上执行了我的代码。我想记录您在该站点上键入的所有内容并将其发送给我吗?完成,没有办法阻止我,因为当 eval 执行时,浏览器会赋予它最高权限。
16赞 Brian 9/18/2008 #2

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

评论

0赞 weaknespase 11/11/2016
Eval 可用于替换缺失的元编程功能,例如模板。我更喜欢紧凑型生成器,而不是无穷无尽的功能列表。
0赞 aoeu256 7/19/2019
只要字符串不是来自用户,或者仅限于浏览器,就可以。JavaScript 具有很大的元编程能力,使用诸如更改原型、obj[member]、Proxy、json.parse、window、decorator 函数(副词)(其中 newf = decorator(oldf)、高阶函数(如 Array.prototype.map(f)、将参数传递给其他函数、通过 {} 的关键字参数。你能告诉我一个你不能做这些而不是 eval 的用例吗?
30赞 Mark Biek 9/18/2008 #3

通常,只有在传递 eval 用户输入时,这才是一个问题。

评论

1赞 Sohan Arafat 9/19/2021
这意味着只是一些简单的页面计算不会造成任何伤害。很高兴知道这一点。
5赞 John Topley 9/18/2008 #4

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

评论

1赞 doubleOrt 9/12/2017
仅当您的服务器端安全性很糟糕时。客户端安全性完全是无稽之谈。
2赞 Matthew Crumley 9/18/2008 #5

除了执行用户提交的代码时可能存在的安全问题之外,大多数情况下,还有一种更好的方法,即不涉及在每次执行代码时重新解析代码。匿名函数或对象属性可以取代 eval 的大多数用法,并且更安全、更快捷。

415赞 Prestaul 9/18/2008 #6
  1. eval 使用不当会打开 注入攻击代码

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

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

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

评论

3赞 Prestaul 2/14/2012
@JeffWalden,很棒的评论。我已经更新了我的帖子,尽管我意识到您发帖已经一年了。Xnzo72,如果你对你的评论有所限定(就像杰夫所做的那样),那么我可能会同意你的看法。Jeff 指出了关键:“多次评估同一字符串可以避免解析开销”。事实上,你错了;#3 适用于许多情况。
7赞 Eduardo Molteni 10/24/2012
@Prestaul:既然假定的攻击者可以使用任何开发人员工具来更改客户端中的 JavaScript,为什么你说 Eval() 会将您的代码开放给注入攻击?还没打开?(当然,我说的是客户端 JavaScript)
71赞 Prestaul 10/26/2012
@EduardoMolteni,我们并不关心(实际上也无法阻止)用户在自己的浏览器中执行 js。我们试图避免的攻击是当用户提供的值被保存,然后放入 javascript 和 eval'd 时。例如,我可能会将我的用户名设置为: 如果你把我的用户名,把它连接到一些脚本中,并在其他人的浏览器中评估它,那么我可以在他们的机器上运行我想要的任何javascript(例如,强制他们+1我的帖子,将他们的数据发布到我的服务器,等等)badHackerGuy'); doMaliciousThings();
3赞 frodeborli 3/12/2014
一般来说,#1 在很多情况下都是正确的,如果不是大多数函数调用的话。eval() 不应该被有经验的程序员挑出来避免,仅仅因为没有经验的程序员滥用它。然而,有经验的程序员往往在他们的代码中有一个更好的架构,由于这种更好的架构,eval()很少被需要,甚至很少被考虑。
4赞 Sid 7/26/2014
@TamilVendhan 当然可以放置断点。您可以通过将语句添加到源代码中来访问 Chrome 为您的评估编码创建的虚拟文件。这将停止在该行上执行程序。然后,您可以添加调试断点,就像它只是另一个 JS 文件一样。debugger;
2赞 Brian 9/18/2008 #7

随着下一代浏览器出现一些 JavaScript 编译器,这可能会成为一个更大的问题。通过 Eval 执行的代码在这些较新的浏览器上的性能可能不如 JavaScript 的其余部分。有人应该做一些分析。

28赞 xtofl 9/18/2008 #8

我想到了两点:

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

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

评论

10赞 Paweł Brewczynski 3/11/2013
如果客户无论如何都可以用我们的代码做任何他/她想要的事情,为什么安全性是一个问题?油猴?
5赞 Felipe Pereira 12/3/2014
@PaulBrewczynski,当用户 A 保存他的部分代码以供使用,然后该一小段代码在用户的 B 浏览器上运行时,就会出现安全问题eval
13赞 Thevs 9/18/2008 #9

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

评论

1赞 Periata Breatta 10/27/2016
虽然这是真的 - 如果你的内容不是动态的,有什么理由使用eval 呢?你可以把代码放在一个函数中,然后调用它!
0赞 Thevs 10/27/2016
举个例子 - 解析来自 Ajax 调用的返回值(如 JSON、服务器定义的字符串等)。
2赞 Periata Breatta 10/29/2016
哦,我明白。我会称这些为动态的,因为客户事先不知道它们是什么,但我现在明白你的意思了。
23赞 Andrew Hedges 9/18/2008 #10

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

评论

31赞 Andrew Hedges 1/26/2012
在我回答这个问题后的 3+ 年里,我对发生的事情的理解,比方说,加深了。实际发生的是创建一个新的执行上下文。查看 dmitrysoshnikov.com/ecmascript/chapter-1-execution-contexts
6赞 Tom 9/18/2008 #11

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

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

4赞 David Plumpton 9/18/2008 #12

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

5赞 MarkR 9/18/2008 #13

只要你知道你在什么上下文中使用它,它就不一定那么糟糕。

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

无论如何,不受信任的客户端 JavaScript 代码都做不了那么多。只要你正在执行的东西来自一个合理的来源,你就没问题。eval()

评论

3赞 Brendan Long 4/5/2011
使用 eval 不是比解析 JSON 慢吗?
0赞 Periata Breatta 10/27/2016
@Qix - 在我的浏览器 (Chrome 53) 上运行该测试显示 evalparse 快一些。
0赞 Qix - MONICA WAS MISTREATED 10/27/2016
@PeriataBreatta 呵呵,奇怪。我想知道为什么。当时我评论说,情况并非如此。然而,Chrome 在运行时的某些方面从版本到另一个版本获得奇怪的性能提升并非闻所未闻。
0赞 jdmayfield 2/8/2018
这里有一个有点老的线程,但从我所读到的内容来看——并不是说我自己追溯了它——JSON.parse 实际上是 eval 在最后阶段的输入。因此,在效率方面,需要更多的工作/时间。但从安全角度来看,为什么不直接解析呢?eval 是一个很棒的工具。将它用于没有其他方法的事情。要通过 JSON 传递函数,有一种方法可以在没有 eval 的情况下做到这一点。JSON.stringify 中的第二个参数允许您运行一个回调,您可以通过 typeof 检查它是否是一个函数。然后获取函数的 .toString()。如果你搜索,有一些关于这方面的好文章。
353赞 bobince 9/18/2008 #14

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

评论

87赞 Mike Spross 3/15/2009
嗯,我想我第一次学习 JavaScript 时很幸运。我总是使用“document.getElementById”来访问DOM;具有讽刺意味的是,我当时才这样做,因为我不知道对象在 JavaScript 中是如何工作的;
5赞 schoetbi 1/3/2011
同意。有时 eval 是可以的,例如,对于来自 Web 服务的 JSON 响应
45赞 alexia 1/11/2011
@schoetbi:你不应该用 JSON 代替吗?JSON.parse()eval()
4赞 Martijn 2/1/2011
@bobince code.google.com/p/json-sans-eval 适用于所有浏览器,github.com/douglascrockford/JSON-js 也是如此。Doug Crockford的json2.js确实在内部使用了eval,但带有检查。此外,它还向前兼容对 JSON 的内置浏览器支持。
9赞 MauganRa 11/23/2012
@bobince 有一种叫做特征检测和 polyfills 的东西来处理缺失的 JSON 库和其他东西(看看 modernizr.com)
14赞 matt lohkamp 9/18/2008 #15

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

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

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

然而,由于上面提到的所有原因,你不应该将它用于你自己的代码,在那里你有完全的控制权 - 它只是没有必要,最好降级到“棘手的 JavaScript hacks”架子上。

评论

1赞 Mahesh 5/20/2014
只需更新上面的代码..--嗨--需要用引号引起来,因为它是一个字符串。eval('al' + 'er' + 't(' + '“嗨!”' + ')');
3赞 Kamila Borowska 9/21/2014
[]["con"+"struc"+"tor"]["con"+"struc"+"tor"]('al' + 'er' + 't(\'' + 'hi there!' + '\')')()
5赞 joshden 4/6/2016
哎呀,有些社交网站限制了alert()但允许eval()?!
4赞 Ian 3/9/2010 #16

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

评论

0赞 aoeu256 7/19/2019
你也可以使用 Function() {},但在服务器上使用它们时要小心,除非你希望用户接管你的服务器哈哈哈。
8赞 Paul Mendoza 1/3/2012 #17

与其他答案一起,我认为 eval 语句不能具有高级最小化。

2赞 Amr Elgarhy 10/8/2014 #18

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

我并不是说你应该用完并开始使用 eval() 到处。事实上,运行的好用例很少 eval() 中。肯定存在代码清晰度问题, 可调试性,当然还有不容忽视的性能。 但是,当您遇到以下情况时,您不应该害怕使用它 eval() 是有道理的。尽量不要先使用它,但不要让任何人吓到它 当 eval() 使用得当。

2赞 Phi 10/25/2014 #19

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

1赞 Carnix 2/13/2015 #20

我不会试图反驳之前所说的任何事情,但我会提供这种 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);

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

评论

4赞 user2144406 4/14/2016
这可以通过 来完成,这里不需要 eval,所以唯一的区别是namespaceToTest[namespaceParts[i]]if(typeof namespaceToTest[namespaceParts[i]] === 'undefined') { namespaceToTest[namespaceParts[i]] = {};else namespaceToTest = namespaceToTest[namespaceParts[i]];
2赞 hkasera 1/30/2016 #21

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

评论

0赞 Jack G 5/10/2018
没有 javascript 引擎无法在代码中找到并评估 100% 保证。因此,它必须随时做好准备。
2赞 Wikened 11/28/2016 #22

这并不总是一个坏主意。以代码生成为例。我最近写了一个名为 Hyperbars 的库,它弥合了 virtual-domhandlebars 之间的差距。它通过解析 handlebars 模板并将其转换为随后由 virtual-dom 使用的超脚本来实现这一点。超标首先生成为字符串,然后再返回它,将其转换为可执行代码。在这种特殊情况下,我发现与邪恶完全相反。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()

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

4赞 12345678 5/8/2017 #23

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

这 函数接受任意字符串并将其作为 JavaScript 代码执行。当代码在 问题是事先知道的(不是在运行时确定的),没有理由使用 eval() 中。 如果代码是在运行时动态生成的,通常有一种更好的方法可以 在没有 eval() 的情况下实现目标。 例如,只需使用方括号表示法即可 访问动态属性更好、更简单:

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

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

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

同样重要的是要记住,将字符串传递给 、 、 在大多数情况下,构造函数类似于使用 和 因此 应避免。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);

使用 new Function() 构造函数类似于 eval(),应该使用 小心。它可能是一个强大的结构,但经常被滥用。 如果你绝对必须 use ,您可以考虑改用 new Function()。eval()

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

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

评论

0赞 Regular Jo 1/17/2018
您能建议如何在没有 eval 的情况下评估函数局部动态变量名称吗?在大多数包含它们的语言中,Eval 函数(和类似函数)是最后的手段,但有时这是必要的。在获取动态变量名称的情况下,是否有任何解决方案更安全?无论如何,javascript 本身并不是为了真正的安全(服务器端显然是主要的防御)。如果您有兴趣,以下是我的 eval 用例,我很想改变它: stackoverflow.com/a/48294208
2赞 Adam Copley 3/1/2018 #24

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

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

*只要你的服务器端点对用户提供的值进行了正确的验证和清理,那么在客户端javascript中解析和评估的内容就无关紧要了。

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

3赞 J D 8/13/2019 #25

编辑:正如 Benjie 的评论所暗示的那样,在 chrome v108 中似乎不再是这种情况,chrome 现在似乎可以处理评估脚本的垃圾回收。

VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV

垃圾回收

浏览器垃圾回收不知道是否可以从内存中删除评估的代码,因此它只是将其存储,直到重新加载页面。 如果您的用户只是很快出现在您的页面上,这还不错,但对于网络应用程序来说,这可能是一个问题。

下面是一个演示该问题的脚本

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

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

像上面的代码这样简单的事情会导致存储少量内存,直到应用程序死亡。 当评估的脚本是一个巨大的函数,并且按间隔调用时,情况会更糟。

评论

1赞 Benjie 1/13/2023
在运行 V8 JS 引擎的 Chrome v108 中似乎并非如此,与 Node 相同。虽然评估版的爬升速度似乎略快,但它花了大约 80 秒才达到 10MB,然后垃圾收集回起始量并继续运行。单击左上角的垃圾桶图标(“收集垃圾”)也具有将内存使用量重置回原始值的相同效果。似乎没有任何泄漏。
1赞 J D 2/4/2023
刚刚测试了它,你是对的,似乎情况可能不再是这样了。谢谢@Benjie