提问人:Brian Singh 提问时间:9/18/2008 最后编辑:Nisarg ShahBrian Singh 更新时间:2/4/2023 访问量:290180
为什么使用 JavaScript eval 函数是个坏主意?
Why is using the JavaScript eval function a bad idea?
答:
我相信这是因为它可以从字符串执行任何 JavaScript 函数。使用它使人们更容易将流氓代码注入应用程序。
评论
主要是,维护和调试要困难得多。这就像一个.您可以使用它,但它使发现问题变得更加困难,并且对以后可能需要进行更改的人也更加困难。goto
评论
通常,只有在传递 eval 用户输入时,这才是一个问题。
评论
除非您 100% 确定正在评估的代码来自受信任的来源(通常是您自己的应用程序),否则这是将您的系统暴露于跨站点脚本攻击的可靠方法。
评论
除了执行用户提交的代码时可能存在的安全问题之外,大多数情况下,还有一种更好的方法,即不涉及在每次执行代码时重新解析代码。匿名函数或对象属性可以取代 eval 的大多数用法,并且更安全、更快捷。
eval 使用不当会打开 注入攻击代码
调试可能更具挑战性 (无行号等)
评估代码执行速度较慢(没有机会编译/缓存评估代码)
编辑:正如 @Jeff Walden 在评论中指出的那样,#3 今天不如 2008 年那么真实。但是,虽然可能会对已编译的脚本进行一些缓存,但这仅限于重复求值而不进行修改的脚本。更可能的情况是,您正在评估每次都经过轻微修改的脚本,因此无法缓存。我们只是说一些 eval 代码执行得更慢。
评论
badHackerGuy'); doMaliciousThings();
debugger;
随着下一代浏览器出现一些 JavaScript 编译器,这可能会成为一个更大的问题。通过 Eval 执行的代码在这些较新的浏览器上的性能可能不如 JavaScript 的其余部分。有人应该做一些分析。
我想到了两点:
安全性(但只要您自己生成要评估的字符串,这可能就不是问题)
性能:直到要执行的代码未知,否则无法优化。(关于 javascript 和性能,当然是 Steve Yegge 的演讲)
评论
eval
除非你让 eval() 成为动态内容(通过 cgi 或 input),否则它与页面中的所有其他 JavaScript 一样安全可靠。
评论
将用户输入传递给 eval() 存在安全风险,但每次调用 eval() 都会创建一个 JavaScript 解释器的新实例。这可能是一个资源占用。
评论
这是一个可能的安全风险,它具有不同的执行范围,并且效率非常低,因为它为代码的执行创建了一个全新的脚本环境。有关详细信息,请参阅此处:eval。
不过,它非常有用,并且适度使用可以添加许多良好的功能。
它大大降低了您对安全性的信心水平。
只要你知道你在什么上下文中使用它,它就不一定那么糟糕。
如果您的应用程序正在使用从某个 JSON 创建对象,该对象已从 XMLHttpRequest 返回到您自己的站点,由您受信任的服务器端代码创建,则这可能不是问题。eval()
无论如何,不受信任的客户端 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 元素的狡猾的旧技巧更可靠。
评论
JSON.parse()
eval()
要记住的一件事是,您通常可以使用 eval() 在其他受限制的环境中执行代码 - 阻止特定 JavaScript 函数的社交网站有时会被 eval 块中分解来愚弄它们 -
eval('al' + 'er' + 't(\'' + 'hi there!' + '\')');
因此,如果你想在可能不允许的地方运行一些 JavaScript 代码(Myspace,我在看着你......),那么 eval() 可能是一个有用的技巧。
然而,由于上面提到的所有原因,你不应该将它用于你自己的代码,在那里你有完全的控制权 - 它只是没有必要,最好降级到“棘手的 JavaScript hacks”架子上。
评论
[]["con"+"struc"+"tor"]["con"+"struc"+"tor"]('al' + 'er' + 't(\'' + 'hi there!' + '\')')()
如果您希望用户输入一些逻辑函数并计算 AND OR,那么 JavaScript eval 函数是完美的。我可以接受两个字符串和,等等。eval(uate) string1 === string2
评论
与其他答案一起,我认为 eval 语句不能具有高级最小化。
这是一篇关于eval的好文章,以及它如何不是邪恶的:http://www.nczonline.net/blog/2013/06/25/eval-isn't-evil-just-misunderstood/
我并不是说你应该用完并开始使用 eval() 到处。事实上,运行的好用例很少 eval() 中。肯定存在代码清晰度问题, 可调试性,当然还有不容忽视的性能。 但是,当您遇到以下情况时,您不应该害怕使用它 eval() 是有道理的。尽量不要先使用它,但不要让任何人吓到它 当 eval() 使用得当。
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
我不会试图反驳之前所说的任何事情,但我会提供这种 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);
编辑:顺便说一句,我不建议(出于前面指出的所有安全原因)根据用户输入来命名对象。不过,我无法想象你有什么好的理由要这样做。不过,我想我会指出这不是一个好主意:)
评论
namespaceToTest[namespaceParts[i]]
if(typeof namespaceToTest[namespaceParts[i]] === 'undefined') { namespaceToTest[namespaceParts[i]] = {};
else namespaceToTest = namespaceToTest[namespaceParts[i]];
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
评论
这并不总是一个坏主意。以代码生成为例。我最近写了一个名为 Hyperbars 的库,它弥合了 virtual-dom 和 handlebars 之间的差距。它通过解析 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()
如果你好奇的话,你可以在这里看到代码生成是如何实现的。
如果你发现在代码中使用了 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()
评论
我甚至会说,如果你在浏览器中运行的javascript中使用,这并不重要。eval()
所有现代浏览器都有一个开发人员控制台,您可以在其中执行任意 javascript,任何半智能开发人员都可以查看您的 JS 源代码,并将他们需要的任何部分放入开发控制台中以执行他们想要的事情。
*只要你的服务器端点对用户提供的值进行了正确的验证和清理,那么在客户端javascript中解析和评估的内容就无关紧要了。
但是,如果您要问它是否适合在PHP中使用,答案是否定的,除非您将任何可能传递给eval语句的值列入白名单。eval()
编辑:正如 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());
}
};
像上面的代码这样简单的事情会导致存储少量内存,直到应用程序死亡。 当评估的脚本是一个巨大的函数,并且按间隔调用时,情况会更糟。
评论
上一个:PHP 密码的安全哈希和盐
下一个:如何使用 PHP 清理用户输入?
评论