正确评估代码的方法

时间:2017-06-20 17:40:30

标签: javascript function eval evaluation expression-evaluation

所以我正在构建一个小应用程序,您可以在其中评估一些JavaScript代码,但我有一个巨大的道德"问题:

最初我想使用eval,但我发现了它的危险,所以我很快找了一个替代方案。

我能找到的最接近的东西是函数构造函数,但是一方面它不会评估简单的代码片段,例如2 + 3,因为它需要return语句,而eval并不是eval,并且它的安全性也不比<!DOCTYPE html> <html> <body> <table width="400" border="0"> <tr> <td width="64" id="Submit"> <button id="myBtn">My Button</button> </td> </tr> <p>Click the button below to disable the button above.</p> <button onclick="myFunction(this)">Try it</button> <script> function myFunction(button) { button.disabled = true; } </script> </body> </html> 好(至少从我收集过的内容)。

还有其他方法来评估字符串,就好像它是代码一样吗?

3 个答案:

答案 0 :(得分:2)

如果您想评估JavaScript代码,请使用eval。危险吗?是。但这只是因为评估JavaScript是危险的。评估JavaScript没有安全的方法。如果您想评估JavaScript,请使用eval

采取一切安全措施。在不了解您希望支持的内容以及计划如何实施的详细信息的情况下,您无法了解应采取的安全预防措施。

这可能很有用:

Is It Possible to Sandbox JavaScript Running In the Browser?

https://github.com/google/caja

答案 1 :(得分:0)

请不要使用eval,无论如何,还有更好的选择。而不是eval,请使用new functioneval是邪恶的,毫无疑问,但大多数人都跳过了eval最邪恶的方面:它让你可以访问本地范围内的变量。回到90年代,回到JIST编译的概念之前,eval听起来像是一个好主意(他们是):只需在代码中动态插入一些额外的行即可。 ;已经逐行执行了。这也意味着eval并没有真正减缓这一切。但是,现在使用JIST编译eval语句的日子对JIST编译器非常沉重,这些编译器在内部完全删除了变量名的概念。对于JIST编译器,为了评估eval语句,它必须弄清楚其所有变量的存储位置,并将它们与evaled语句中的未知全局变量匹配。如果你真的技术化,问题会更深入。

但是,对于new function,JIST编译器不必进行任何昂贵的变量名称查找:整个代码块是自包含的并且在全局范围内。例如,请使用以下非常低效的eval代码段。请注意,这仅仅是为了举例。在生产代码中,您甚至不应该使用eval或new Function从内容已知的字符串生成函数。

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

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

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

注意区别?有一个主要问题:eval在本地范围内执行,new Function在全局范围内执行。

现在,进入下一个问题:安全性。有很多关于安全性如何困难的讨论,是的,使用eval几乎是不可能的(例如,如果将整个代码包装在沙盒函数中,那么你所要做的就是过早地结束函数并开始一个新的一个在当前范围内自由执行代码)。但是,使用new 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);
}

然后,使用白名单,按照您想要的方式进行操作,然后就像sandboxed_function一样使用new Function。例如:

&#13;
&#13;
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();
&#13;
<pre id="output"></pre>
&#13;
&#13;
&#13;

至于编写要在此严格沙箱下运行的代码,您可能会问,如果窗口未定义,我如何测试方法的存在。有两种解决方案。 #1只是简单地使用typeof。

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

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

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

因此,总而言之,我希望我的代码片段能让您深入了解eval的更好,更好,更清晰的替代方案。而且我希望我能让你有更大的目标:对eval嗤之以鼻。至于浏览器兼容性,虽然sandboxed_function将在IE9中运行,但为了实际沙盒任何东西,IE10 +是必需的。这是因为"use-strict"声明对于消除像下面那样的大多数偷偷摸摸的沙盒破坏方式非常重要。

&#13;
&#13;
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);
&#13;
<pre id="output"></pre>
&#13;
&#13;
&#13; 最后一个评论是,如果您要允许事件API进入沙盒环境,那么您必须小心:view属性可以是一个窗口对象,因此您必须删除它太。还有其他一些事情,但我建议彻底研究并在Chrome控制台中探索这些对象。

答案 2 :(得分:0)

您可以轻松地在JS中创建自己的 JS解释器。我为www.Photopea.com做了这样的事情(文件 - 脚本,我想让用户在PSD文档上执行脚本)。

Acorn是一个高级JS解析器,它接受一个字符串(JS代码)并返回一个语法树。然后,从语法树的根开始,逐个执行命令。

递归地“越过”树。使用环境的JS调用堆栈作为解释代码的调用堆栈。使用JS对象{var1: ..., var2: ...}来存储每个执行空间中的变量值(全局,函数中的本地......)。

您可以允许该代码通过某个界面从外部环境访问数据,或者使其完全沙箱化。我认为制作自己的翻译会花一个星期的时间,但我在6个小时内完成了它:)