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

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

281400 次浏览

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

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

通常只有在传递eval用户输入时才会出现问题。

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

如果您正在执行用户提交的代码,除了可能存在的安全问题之外,大多数情况下,有一种更好的方法,无需在每次执行时都重新解析代码。匿名函数或对象属性可以替代eval的大部分用途,而且更安全、更快。

  1. eval使用不当打开你的 注入攻击

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

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

编辑:正如杰夫·沃尔登在评论中指出的那样,第三条在今天已经不像2008年那么正确了。然而,虽然可能会发生一些已编译脚本的缓存,但这将只局限于没有修改地重复计算的脚本。更可能的情况是,您正在计算的脚本每次都经历了轻微的修改,因此无法缓存。我们就说someeval代码执行得更慢。

随着带有JavaScript编译器的下一代浏览器问世,这可能会成为一个更大的问题。通过Eval执行的代码在这些新浏览器上的表现可能不如JavaScript的其他部分。应该有人来做侧写。

我想到两点:

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

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

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

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

这可能存在安全风险,它具有不同的执行范围,并且效率非常低,因为它为代码的执行创建了一个全新的脚本环境。更多信息见这里:eval

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

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

如果你知道你在什么环境中使用它,它并不一定那么糟糕。

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

不可信的客户端JavaScript代码无论如何也做不了那么多。如果您正在执行eval()的对象来自一个合理的来源,那么就没问题。

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元素更可靠。)

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

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

因此,如果您希望运行一些JavaScript代码,否则它可能不被允许(Myspace,我在看你…),那么eval()可能是一个有用的技巧。

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

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

和其他答案一样,我不认为eval语句可以有高级最小化。

这是一篇谈论eval以及它如何不是邪恶的好文章: # EYZ0 < / p >

我不是说你应该跑出去开始使用eval() 无处不在。事实上,很少有好的运行用例 Eval()。代码的清晰度肯定是有问题的, 可调试性,当然性能也不容忽视。 但你不应该害怕使用它当你遇到这样的情况 Eval()是有意义的。试着先不使用它,但不要让任何人害怕 当eval()时,你会认为你的代码更脆弱或不安全

eval()功能非常强大,可用于执行JS语句或计算表达式。但问题不是关于eval()的使用,而是让我们说说你用eval()运行的字符串是如何受到恶意方的影响的。最后,您将运行恶意代码。权力越大,责任越大。所以,当你在使用它的时候,就要明智地使用它。 这与eval()函数没有太大关系,但这篇文章有很好的信息: http://blogs.popart.com/2009/07/javascript-injection-attacks/ 如果你正在寻找eval()的基础,看这里: # EYZ0 < / p >

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

编辑:顺便说一下,我不建议(出于之前指出的所有安全原因)根据用户输入来确定对象名称。但我想不出你有什么理由这么做。不过,我还是想指出这不是一个好主意:)

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

但是,如果引擎在代码中发现了eval(..),它本质上必须假设它对标识符位置的所有感知都可能是无效的,因为它在lexlexation时无法确切地知道您可以传递给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

这并不总是一个坏主意。以代码生成为例。我最近写了一个名为高气压的库,它弥合了virtual-dom车把之间的差距。它通过解析一个句柄模板并将其转换为随后被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()的性能不是问题,因为您只需要解释一次生成的字符串,然后多次重用可执行输出。

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

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

< p >这 function接受任意字符串,并将其作为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(),因此 应该避免。< / p > 在幕后,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()代替。< / p >

有一个小潜力 好处,因为在new Function()中求值的代码将在本地函数中运行 范围,因此在被求值的代码中使用var定义的任何变量都不会变成 自动全局。< / p > 防止自动全局变量的另一种方法是将

. eval()调用

我想说的是,如果你在javascript中使用eval()并不重要,因为它是在浏览器中运行的。

所有现代浏览器都有一个开发者控制台,在那里你可以执行任意的javascript,任何半聪明的开发者都可以查看你的JS源代码,并将他们需要的任何部分放入开发控制台来做他们想做的事情。

*只要你的服务器端有正确的验证&净化用户提供的值,它不应该被什么被解析和评估在你的客户端javascript。

如果你问是否适合在PHP中使用eval(),答案是没有,除非你白名单任何值,可以传递给你的eval语句。

垃圾收集

浏览器的垃圾收集不知道被评估的代码是否可以从内存中删除,所以它只是一直存储它,直到页面被重新加载。 如果你的用户只是在你的页面上停留很短的时间,这不是太糟糕,但这可能是一个webapp的问题。< / p >

下面是演示该问题的脚本

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

document.getElementById("evalLeak").onclick = (e) => {
for(let x = 0; x < 100; x++) {
eval(x.toString());
}
};
像上面这样简单的代码会导致少量的内存被存储,直到应用程序死亡。 当被赋值的脚本是一个巨大的函数,并且在interval.

时调用,情况会更糟