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

Security considerations using "new Function(...)" (during rendertime, expression coming from my Javascript sources)

本文关键字:表达式 我的 Javascript 源代码 自我 Function new 安全 使用 注意事项      更新时间:2023-09-26

我想用new Function(...)从非常精简的代码中生成一个函数。我想这样做是为了

  • 避免自己解析表达式,并
  • 尽可能灵活。

我尽可能避免eval()。但我不确定它是否足够安全,可以使用new Function(...)这也被称为容易出现安全漏洞。

背景

我想管理菜单按钮的状态。所以,在定义按钮时,我想写一些类似的东西

 {
 ..., // More button definition
 state: "isInEditmode && (isWidgetSelected || isCursorInWidget),
 ...
 }

在处理多个事件期间的状态更改时,我将根据 states 属性中的状态检查(汇总(当前总体状态对象的状态。

所以我将在渲染时生成一个函数,并将其附加为 DOM 对象属性,而不是 DOM 属性

 ...
 $el.stateFn = new Function("stateObj", "with (stateObj) {return " + item.state + ";}");
 ...

测试状态:

 visible = $el.stateFn.call(currentStates, currentStates);

with语句帮助我将当前state对象的属性作为变量提供,以便上述表达式不需要类似 obj.isInEditmode 的东西。

安全问题

在我看来,这不会引入安全漏洞,因为附加到 DOM 对象的函数是在渲染时生成的并从源代码读取的。还是我错了?我应该避免这种情况吗?

性能提示是值得赞赏的(评论((我认为只要我在渲染时评估一次新Function,这是可以接受的(。

编辑 1

  • 我正在使用骨干.js。使用另一个框架是毫无疑问的。
  • 某些菜单项需要绑定到不同甚至多个模型。
  • 委派(或外观/代理?(模型相当可观。

如果允许用户输入在代码中中断,则在安全性方面,两者都同样糟糕。但是,在维护方面,当本地评估弄乱您的范围并导致动态范围时,您不必担心隐藏的错误。

在性能方面,new Function生成的函数与任何其他函数完全相同。生成速度较慢,但与eval不同,它不会导致包含范围不可优化。

事实上,new Function可用于在以下情况下提高性能:

//Will behave like function a( obj ) { return obj.something }
function makePropReader( propName ) {
    return new Function( "obj", "return obj." + propName );
}

构造的函数将比此处返回的函数执行得更好:

function makePropReader( propName ) {
     return function( obj ) {
         return obj[propName];
     }
}

由于必须从闭包上下文中动态读取propName,并在每次调用对象时对对象进行动态读取。

无论如何,请不要使用 eval 。有一个更好的选择。不要使用 eval ,而是使用 Function 构造函数。 eval是邪恶的,这是毫无疑问的,但大多数人跳过了eval最邪恶的方面:它使您可以访问局部范围内的变量。早在 90 年代,早在 JIST 编译的概念出现之前,eval听起来是个好主意(确实如此(:只需在你已经逐行执行的代码中动态插入一些额外的行。这也意味着eval并没有真正放慢速度。然而,现在使用JST编译eval语句对JST编译器来说非常繁重,因为JST编译器在内部完全删除了变量名的概念。对于 JIST 编译器,为了评估 eval 语句,它必须找出其所有变量的存储位置,并将它们与 evaled 语句中找到的未知全局变量进行匹配。如果你真的有技术性,问题会更深入。

但是,有了Function,JIST编译器不必做任何昂贵的变量名查找:整个代码块是独立的,并且在全局范围内。例如,采用以下效率极低的eval片段。请注意,这只是一个示例。在生产代码中,您甚至不应该使用 eval 或 Function 从内容已知的字符串生成函数。

var a = {
    prop: -1
};
var k = eval('(function(b){return a.prop + b;})');
alert( k(3) ); // will alert 2

现在,让我们看一下更好的Function替代方案。

var a = {
    prop: -1
};
var k = (Function('a', 'b', 'return a.prop + b')).bind(undefined, a);
alert( k(3) ); // will alert 2

注意到区别了吗?有一个主要的: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);
}
然后,

摆弄白名单,按照您想要的方式获取它,然后您可以像Function一样使用sandboxed_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);
}
var myfunc = sandboxed_function('return "window = " + window + "''ndocument = " + document + "''nBoolean = " + Boolean');
output.textContent = myfunc();
<pre id="output"></pre>

至于编写要在这个严格的沙箱下运行的代码,你可能会问,如果 window 未定义,我该如何测试方法的存在。对此有两种解决方案。#1 只是简单地像这样使用类型。

output.textContent = 'typeof foobar = ' + typeof foobar;
<div id="output"></div>

正如您在上面的代码中看到的,使用 typeof 不会抛出错误,而只会返回 undefined。检查全局的第二个主要方法是使用 try/catch 方法。

try {
    if (foobar)
        output.textContent = 'foobar.constructor = ' + foobar.constructor;
    else
        output.textContent = 'foobar.constructor = undefined';
} catch(e) {
    output.textContent = 'foobar = undefined';
}
<div id="output"></div>

所以,总而言之,我希望我的代码片段能让你对一个更好、更好、更干净的 eval 替代方案有所了解。我希望我渴望你有一个更大的目标:冷落eval。至于浏览器兼容性,虽然sandboxed_function将在IE9中运行,但为了使其实际沙盒化任何东西,IE10+是必需的。这是因为"use-strict"声明对于消除许多像下面这样的偷偷摸摸的沙盒破坏方式非常重要。

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(){
    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 (function(){
    var snatched_window = this; // won't work in strict mode where the this
                                // variable doesn't need to be an object
    return snatched_window;
}).call(undefined)`);
output.textContent = "Successful broke out: " + (myfunc() === window);
<pre id="output"></pre>
最后一条评论是,如果要允许事件 API 进入沙盒环境,则必须小心:view 属性可以是窗口对象,因此您也必须擦除它。还有其他几件事,但我建议彻底研究并探索Chrome控制台中的对象。

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

这些天答案被认为很危险的旧线程。 new Function()仍然允许访问全局变量。因此,当攻击者有机会影响函数字符串时 - 这通常是考虑new Function并且难以保证它不能恶意完成的原因 - 可以读取和修改任何全局。从那一刻起祝你好运:-(

这就是为什么从此处提到的CSP(内容安全策略(的角度来看,new Functioneval属于同一类别的原因。

例:

a = 10
> 10
b = new Function('a = 20; return 42')
> function(...)
a
> 10
b()
> 42
a
> 20

正如你所说,你只会在你自己编写的代码上执行此操作 - 我会说这很好。 在任何情况下,new Function()绝对比使用eval()要好。您不会弄乱任何局部变量,并且通过使用fn.call强制执行自己的上下文。

在我看来,如果您使用的是支持 2 路数据绑定的 MVC 或 MVVM 框架,您尝试解决的问题将相当简单。 即更改 UI 会更新支持模型,更新模型将自动为您刷新 UI。

例如,淘汰赛.js。在这种情况下,可见数据绑定是合适的。