使用“new Function(...)”的安全注意事项(在渲染期间,来自我的 Javascript 源的表达式)

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在渲染期间评估一次新的,这是可以接受的)。

编辑 1

  • 我正在使用 Backbone.js。使用另一个框架是不可能的。
  • 某些菜单项需要绑定到不同甚至多个模型。
  • 委托(或门面/代理?)模型相当可观。

Jac*_*fin 7

无论如何,请永远不要使用 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_functionFunction. 例如:

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)
最后最后一点是,如果您打算允许事件 API 进入您的沙盒环境,那么您必须小心:该属性view可以是一个窗口对象,因此您也必须删除它。还有其他一些事情,但我建议彻底研究并探索 Chrome 控制台中的对象。

最后,请注意,这Function是一个非常独特的构造函数,它返回一个函数而不是对象实例,因此无需使用new.


Esa*_*ija 6

如果允许用户输入在代码中出现,那么在安全方面两者都同样糟糕。但是,维护方面,当本地 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从闭包上下文中动态读取并在每次调用对象时对其进行动态读取。