try*_*lly 5 javascript security evaluation eval code-injection
我想用new Function(...)非常精简的代码生成一个函数。我想这样做
我eval()尽可能避免。但我不确定它是否足够安全使用new Function(...),这也被称为容易出现安全漏洞。
我想管理菜单按钮的状态。所以,在定义按钮时,我想写一些类似的东西
{
..., // More button definition
state: "isInEditmode && (isWidgetSelected || isCursorInWidget),
...
}
Run Code Online (Sandbox Code Playgroud)
在处理多个事件期间的 statechange 时,我将根据属性中的状态检查(总结)当前整体状态对象的状态states。
因此,我将在渲染时生成一个函数并将其附加为 DOM 对象属性,而不是这样的 DOM 属性:
...
$el.stateFn = new Function("stateObj", "with (stateObj) {return " + item.state + ";}");
...
Run Code Online (Sandbox Code Playgroud)
测试状态:
visible = $el.stateFn.call(currentStates, currentStates);
Run Code Online (Sandbox Code Playgroud)
该with语句帮助我提供当前state对象的属性作为变量,这样上面的表达式就不需要像obj.isInEditmode.
在我看来,这不会引入安全漏洞,因为附加到 DOM 对象的函数是在渲染时生成并从源读取的。还是我错了?我应该避免这种情况吗?
性能提示表示赞赏(评论)(我认为只要我Function在渲染期间评估一次新的,这是可以接受的)。
无论如何,请永远不要使用 eval。有一个更好的选择。而不是eval使用Function构造函数。eval是邪恶的,毫无疑问,但大多数人都会跳过 的最邪恶的方面eval:它使您可以访问本地范围内的变量。早在 90 年代,JIST 编译概念出现之前,eval听起来是一个好主意(确实如此):只需将一些额外的行动态插入到已经逐行执行的代码中即可。这也意味着evals 并没有真正减慢速度。然而,现在的 JIST 编译语句eval对 JIST 编译器来说非常繁重,它在内部完全删除了变量名称的概念。对于 JIST 编译器,为了评估 eval 语句,它必须找出所有变量的存储位置,并将它们与 eval 语句中找到的未知全局变量进行匹配。如果你真正掌握了技术,问题就会变得更加严重。
但是,使用Function,JIST 编译器不必执行任何昂贵的变量名查找:整个代码块是独立的并且位于全局范围内。例如,采用以下效率极低的eval代码片段。请注意,这仅用于作为示例。在生产代码中,您甚至不应该使用 eval 或Function从内容已知的字符串生成函数。
var a = {
prop: -1
};
var k = eval('(function(b){return a.prop + b;})');
alert( k(3) ); // will alert 2
Run Code Online (Sandbox Code Playgroud)
现在,让我们看看更好的Function选择。
var a = {
prop: -1
};
var k = (Function('a', 'b', 'return a.prop + b')).bind(undefined, a);
alert( k(3) ); // will alert 2
Run Code Online (Sandbox Code Playgroud)
注意到区别了吗?有一个主要的问题: 是eval在本地范围内执行的,而 是Function在全局范围内执行的。
现在,讨论下一个问题:安全性。有很多关于安全性如何困难的讨论,是的,使用 eval 几乎是不可能的(例如,如果您将整个代码包装在沙箱函数中,那么您所要做的就是过早结束该函数并开始一个新的函数)一种在当前范围内自由执行代码的方法)。但是,使用Function,您可以轻松(但不是最有效)沙箱任何内容。看下面的代码。
var whitelist = ['Math', 'Number', 'Object', 'Boolean', 'Array'];
var blacklist = Object.getOwnPropertyNames(window).filter(function(x){
return whitelist.indexOf(x) === -1 && !/^[^a-zA-Z]|\W/.test(x)
});
var listlen = blacklist.length;
var blanklist = (new Array(listlen+1)).fill(undefined);
function sandboxed_function(){
"use-strict";
blacklist.push.apply(blacklist, arguments);
blacklist[blacklist.length-1] =
'"use-strict";' + arguments[arguments.length-1];
var newFunc = Function.apply(
Function,
blacklist
);
blacklist.length = listlen;
return newFunc.bind.apply(newFunc, blanklist);
}
Run Code Online (Sandbox Code Playgroud)
然后,摆弄白名单,按照你想要的方式得到它,然后你就可以使用sandboxed_function像Function. 例如:
var a = {
prop: -1
};
var k = eval('(function(b){return a.prop + b;})');
alert( k(3) ); // will alert 2
Run Code Online (Sandbox Code Playgroud)
var a = {
prop: -1
};
var k = (Function('a', 'b', 'return a.prop + b')).bind(undefined, a);
alert( k(3) ); // will alert 2
Run Code Online (Sandbox Code Playgroud)
至于编写在这个严格的沙箱下运行的代码,您可能会问,如果 window 未定义,我如何测试方法是否存在。对此有两种解决方案。#1 只是像这样简单地使用 typeof 。
var whitelist = ['Math', 'Number', 'Object', 'Boolean', 'Array'];
var blacklist = Object.getOwnPropertyNames(window).filter(function(x){
return whitelist.indexOf(x) === -1 && !/^[^a-zA-Z]|\W/.test(x)
});
var listlen = blacklist.length;
var blanklist = (new Array(listlen+1)).fill(undefined);
function sandboxed_function(){
"use-strict";
blacklist.push.apply(blacklist, arguments);
blacklist[blacklist.length-1] =
'"use-strict";' + arguments[arguments.length-1];
var newFunc = Function.apply(
Function,
blacklist
);
blacklist.length = listlen;
return newFunc.bind.apply(newFunc, blanklist);
}
Run Code Online (Sandbox Code Playgroud)
var whitelist = ['Math', 'Number', 'Object', 'Boolean', 'Array'];
var blacklist = Object.getOwnPropertyNames(window).filter(function(x){
return whitelist.indexOf(x) === -1 && !/^[^a-zA-Z]|\W/.test(x)
});
var listlen = blacklist.length;
var blanklist = (new Array(listlen+1)).fill(undefined);
function sandboxed_function(){
"use-strict";
blacklist.push.apply(blacklist, arguments);
blacklist[blacklist.length-1] =
'"use-strict";' + arguments[arguments.length-1];
var newFunc = Function.apply(
Function,
blacklist
);
blacklist.length = listlen;
return newFunc.bind.apply(newFunc, blanklist);
}
var myfunc = sandboxed_function('return "window = " + window + "\\ndocument = " + document + "\\nBoolean = " + Boolean');
output.textContent = myfunc();Run Code Online (Sandbox Code Playgroud)
正如您在上面的代码中看到的,使用 typeof 不会抛出错误,而只会返回 undefined。检查全局的第二个主要方法是使用 try/catch 方法。
<pre id="output"></pre>Run Code Online (Sandbox Code Playgroud)
output.textContent = 'typeof foobar = ' + typeof foobar;Run Code Online (Sandbox Code Playgroud)
因此,总而言之,我希望我的代码片段能让您深入了解 eval 的更好、更好、更干净的替代方案。我希望我已经激励你实现一个更伟大的目标:冷落 eval。至于浏览器兼容性,虽然sandboxed_function将在 IE9 中运行,但为了使其真正沙箱任何内容,需要 IE10+。这是因为该"use-strict"声明对于消除像下面这样的许多偷偷摸摸的沙箱破坏方式非常重要。
<div id="output"></div>Run Code Online (Sandbox Code Playgroud)
try {
if (foobar)
output.textContent = 'foobar.constructor = ' + foobar.constructor;
else
output.textContent = 'foobar.constructor = undefined';
} catch(e) {
output.textContent = 'foobar = undefined';
}Run Code Online (Sandbox Code Playgroud)
view可以是一个窗口对象,因此您也必须删除它。还有其他一些事情,但我建议彻底研究并探索 Chrome 控制台中的对象。
最后,请注意,这Function是一个非常独特的构造函数,它返回一个函数而不是对象实例,因此无需使用new.
如果允许用户输入在代码中出现,那么在安全方面两者都同样糟糕。但是,维护方面,当本地 eval 与范围混淆并导致动态范围时,您不必担心隐藏的错误。
在性能方面,由 生成的函数new Function与任何其他函数完全相同。生成速度较慢,但eval不会导致包含范围不可优化。
事实上,new Function可用于在以下情况下提高性能:
//Will behave like function a( obj ) { return obj.something }
function makePropReader( propName ) {
return new Function( "obj", "return obj." + propName );
}
Run Code Online (Sandbox Code Playgroud)
function makePropReader( propName ) {
return function( obj ) {
return obj[propName];
}
}
Run Code Online (Sandbox Code Playgroud)
由于必须propName从闭包上下文中动态读取并在每次调用对象时对其进行动态读取。