eval函数是动态生成代码的强大而简便的方法,那么有哪些警告?
eval函数是动态生成代码的强大而简便的方法,那么有哪些警告?
Answers:
错误使用eval会使您的代码遭受注入攻击
调试可能会更具挑战性(没有行号等)
评估的代码执行速度较慢(没有机会编译/缓存评估的代码)
编辑:正如@Jeff Walden在评论中指出的那样,今天#3的真实性要低于2008年。但是,尽管可能会缓存一些已编译脚本,但这仅限于未经修改就重复的脚本。更有可能的情况是您正在评估每次都经过略微修改且因此无法缓存的脚本。我们只说某些评估代码的执行速度较慢。
badHackerGuy'); doMaliciousThings();
如果使用我的用户名,将其连接到某个脚本中并在其他人的浏览器中进行评估,那么我可以在他们的计算机上运行我想要的任何javascript(例如,强制他们为我的帖子+1,将其数据发布到我的服务器等)
debugger;
语句添加到源代码中来访问由Chrome创建的虚拟文件,以用于您的无效代码。这将停止在该行上执行程序。然后,您可以像其他JS文件一样添加调试断点。
评估并不总是邪恶的。有时它是完全合适的。
但是,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元素的狡猾的老技巧更可靠。)
JSON.parse()
,而不是eval()
用于JSON?
我相信这是因为它可以从字符串执行任何JavaScript函数。使用它可以使人们更容易地将恶意代码注入到应用程序中。
我想到两点:
安全性(但是,只要您生成要自己评估的字符串,这可能就不是问题了)
性能:除非要执行的代码未知,否则无法对其进行优化。(关于javascript和性能,当然是Steve Yegge的介绍)
eval
然后在用户B的浏览器上运行该小段代码时,就会出现安全问题
将用户输入传递给eval()会带来安全风险,但是每次调用eval()都会创建一个新的JavaScript解释器实例。这可能是资源浪费。
主要是,它很难维护和调试。就像一个goto
。您可以使用它,但是它使发现问题变得更加困难,并且使以后可能需要进行更改的人员变得更加困难。
要记住的一件事是,您经常可以在其他受限制的环境中使用eval()执行代码-阻止特定JavaScript功能的社交网站有时可能会通过在eval块中将它们分解而被愚弄-
eval('al' + 'er' + 't(\'' + 'hi there!' + '\')');
因此,如果您正在寻找一些可能无法通过其他方式运行的JavaScript代码(Myspace,我在看着您...),那么eval()可能是一个有用的技巧。
但是,由于上述所有原因,您不应该将其用于拥有完全控制权的自己的代码中-只是没有必要,而且最好将其降级为“棘手的JavaScript骇客”工具。
[]["con"+"struc"+"tor"]["con"+"struc"+"tor"]('al' + 'er' + 't(\'' + 'hi there!' + '\')')()
除非您让eval()动态内容(通过cgi或输入),否则它与页面中所有其他JavaScript一样安全可靠。
我知道这个讨论很古老,但是我真的很喜欢Google的这种方法,并希望与他人分享这种感觉;)
另一件事是,您越了解越多,您就会尝试理解,最后您只是不相信某事是好是坏,只是因为有人这么说:)这是一部非常鼓舞人心的视频,帮助我自己思考了更多:)好的做法很好,但是请不要轻率地使用它们:)
只要您知道使用它的上下文,并不一定很糟。
如果您的应用程序正在使用eval()
从XMLHttpRequest返回的JSON创建对象到您自己的站点(由受信任的服务器端代码,则可能不是问题。
无论如何,不受信任的客户端JavaScript代码不能做太多事情。只要您执行的事情eval()
来自合理的来源,就可以了。
如果您希望用户输入一些逻辑函数并求与与或,则JavaScript eval函数是理想的选择。我可以接受两个字符串和eval(uate) string1 === string2
,等等。
如果在代码中发现使用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以及它不是邪恶的好文章:http : //www.nczonline.net/blog/2013/06/25/eval-isnt-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/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/eval
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之间的鸿沟。它通过解析车把模板并将其转换为超级脚本(随后由虚拟域使用)来实现。超级脚本首先作为字符串生成,然后在将其返回之前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()
在浏览器中运行的javascript 并没有关系。*(caveat)
所有现代的浏览器都有一个开发人员控制台,您可以在其中执行任意JavaScript,任何半聪明的开发人员都可以查看您的JS源,并将它们需要的所有内容放入开发控制台中以完成所需的操作。
*只要您的服务器端点具有对用户提供的值的正确验证和清除,则在客户端javascript中解析和评估的内容无关紧要。
但是,如果您询问是否适合eval()
在PHP中使用,答案是“ 否”,除非您将可能传递给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]];
垃圾收集
浏览器的垃圾回收不知道是否可以将已评估的代码从内存中删除,因此只能将其保存直到重新加载页面。如果您的用户不久才出现在您的页面上,也不错,但这对于webapp来说可能是个问题。
这是演示问题的脚本
https://jsfiddle.net/CynderRnAsh/qux1osnw/
document.getElementById("evalLeak").onclick = (e) => {
for(let x = 0; x < 100; x++) {
eval(x.toString());
}
};
与上述代码一样简单的操作会导致存储少量内存,直到应用程序终止。当逃避的脚本是一个巨大的函数并按间隔调用时,情况更糟。