我想知道是否可以对浏览器中运行的JavaScript进行沙盒处理,以防止访问通常在HTML页面中运行的JavaScript代码可用的功能。
例如,假设我想为最终用户提供JavaScript API,让他们定义在发生“有趣事件”时要运行的事件处理程序,但是我不希望那些用户访问window
对象的属性和功能。我能做到吗?
在最简单的情况下,假设我要阻止用户致电alert
。我可以想到的几种方法是:
- 重新定义
window.alert
全局。我认为这不是一种有效的方法,因为页面中运行的其他代码(即用户未在其事件处理程序中编写的内容)可能要使用alert
。 - 将事件处理程序代码发送到服务器进行处理。我不确定将代码发送到服务器进行处理是否正确,因为事件处理程序需要在页面的上下文中运行。
服务器处理用户定义的函数然后生成要在客户端上执行的回调的解决方案也许可行?即使该方法可行,还有更好的方法来解决此问题?
Google Caja是一种源到源的翻译器,它“允许您在页面中内嵌不受信任的第三方HTML和JavaScript,并且仍然是安全的”。
使用ADsafe可以安全地将访客代码(例如第三方脚本广告或窗口小部件)放置在任何网页上。ADsafe定义了JavaScript的子集,该子集的功能足以允许来宾代码执行有价值的交互,同时防止恶意或意外损坏或入侵。可以通过JSLint之类的工具对ADsafe子集进行机械验证,因此无需人工检查即可查看来宾代码的安全性。ADsafe子集还执行良好的编码习惯,从而增加了来宾代码正确运行的可能性。
通过查看项目GitHub存储库中的template.html
和template.js
文件,可以看到有关如何使用ADsafe的示例。
我创建了一个名为jsandbox的沙箱库,该库使用Web工作者对评估的代码进行沙箱处理。它还具有一种输入方法,用于显式提供原本无法获取的沙盒代码数据。
以下是API的示例:
jsandbox
.eval({
code : "x=1;Math.round(Math.pow(input, ++x))",
input : 36.565010597564445,
callback: function(n) {
console.log("number: ", n); // number: 1337
}
}).eval({
code : "][];.]\\ (*# ($(! ~",
onerror: function(ex) {
console.log("syntax error: ", ex); // syntax error: [error object]
}
}).eval({
code : '"foo"+input',
input : "bar",
callback: function(str) {
console.log("string: ", str); // string: foobar
}
}).eval({
code : "({q:1, w:2})",
callback: function(obj) {
console.log("object: ", obj); // object: object q=1 w=2
}
}).eval({
code : "[1, 2, 3].concat(input)",
input : [4, 5, 6],
callback: function(arr) {
console.log("array: ", arr); // array: [1, 2, 3, 4, 5, 6]
}
}).eval({
code : "function x(z){this.y=z;};new x(input)",
input : 4,
callback: function(x) {
console.log("new x: ", x); // new x: object y=4
}
});
正如其他回复中所提到的,将代码监禁在沙盒iframe中(无需将其发送到服务器端)并与消息进行通信就足够了。我建议看一下我创建的一个小型库,主要是因为需要向不受信任的代码提供一些API,就像问题中所描述的那样:有机会将特定的一组函数直接导出到沙箱中,不受信任的代码运行。还有一个演示,它执行用户在沙箱中提交的代码:
http://asvd.github.io/jailed/demos/web/console/
我认为js.js在这里值得一提。这是用JavaScript编写的JavaScript解释器。
它比本地JS慢200倍,但是它的性质使其成为一个完美的沙箱环境。另一个缺点是它的大小-接近600 kb,在某些情况下对于台式机是可以接受的,但对于移动设备而言是不可接受的。
@RyanOHara的Web worker沙箱代码的改进版本,位于单个文件中(无需额外的eval.js
文件)。
function safeEval(untrustedCode)
{
return new Promise(function (resolve, reject)
{
var blobURL = URL.createObjectURL(new Blob([
"(",
function ()
{
var _postMessage = postMessage;
var _addEventListener = addEventListener;
(function (obj)
{
"use strict";
var current = obj;
var keepProperties = [
// required
'Object', 'Function', 'Infinity', 'NaN', 'undefined', 'caches', 'TEMPORARY', 'PERSISTENT',
// optional, but trivial to get back
'Array', 'Boolean', 'Number', 'String', 'Symbol',
// optional
'Map', 'Math', 'Set',
];
do {
Object.getOwnPropertyNames(current).forEach(function (name) {
if (keepProperties.indexOf(name) === -1) {
delete current[name];
}
});
current = Object.getPrototypeOf(current);
}
while (current !== Object.prototype);
})(this);
_addEventListener("message", function (e)
{
var f = new Function("", "return (" + e.data + "\n);");
_postMessage(f());
});
}.toString(),
")()"], {type: "application/javascript"}));
var worker = new Worker(blobURL);
URL.revokeObjectURL(blobURL);
worker.onmessage = function (evt)
{
worker.terminate();
resolve(evt.data);
};
worker.onerror = function (evt)
{
reject(new Error(evt.message));
};
worker.postMessage(untrustedCode);
setTimeout(function () {
worker.terminate();
reject(new Error('The worker timed out.'));
}, 1000);
});
}
测试一下:
https://jsfiddle.net/kp0cq6yw/
var promise = safeEval("1+2+3");
promise.then(function (result) {
alert(result);
});
它应该输出6
(在Chrome和Firefox中测试)。
所有浏览器供应商和HTML5规范都在朝着实际的沙箱属性迈进,以允许使用沙箱iframe进行操作-但它仍然仅限于iframe粒度。
通常,任何程度的正则表达式等都无法安全地清理任意用户提供的JavaScript,因为它会退化为停止问题:-/
与内置浏览器实现的框架版本相比,独立的Java解释器更可能产生强大的沙箱。Ryan已经提到过 js.js,但是最新的项目是JS-Interpreter。这些文档介绍了如何向解释器公开各种功能,但是其范围非常有限。
这是一个丑陋的方法,但也许对您有用,我将所有全局变量都放入了沙箱范围中,并重新定义了它们,并添加了严格模式,以使它们无法使用匿名函数获取全局对象。
function construct(constructor, args) {
function F() {
return constructor.apply(this, args);
}
F.prototype = constructor.prototype;
return new F();
}
// Sanboxer
function sandboxcode(string, inject) {
"use strict";
var globals = [];
for (var i in window) {
// <--REMOVE THIS CONDITION
if (i != "console")
// REMOVE THIS CONDITION -->
globals.push(i);
}
globals.push('"use strict";\n'+string);
return construct(Function, globals).apply(inject ? inject : {});
}
sandboxcode('console.log( this, window, top , self, parent, this["jQuery"], (function(){return this;}()));');
// => Object {} undefined undefined undefined undefined undefined undefined
console.log("return of this", sandboxcode('return this;', {window:"sanboxed code"}));
// => Object {window: "sanboxed code"}
https://gist.github.com/alejandrolechuga/9381781
使用NISP,您将能够进行沙盒评估。尽管您编写的表达式并非完全是JS,但是您将编写s-expressions。是不需要大量编程的简单DSL的理想选择。
截至2019年,vm2似乎是在Node.js中运行JavaScript的最受欢迎和最经常更新的解决方案。我不知道前端解决方案。
1)假设您要执行以下代码:
var sCode = "alert(document)";
现在,假设您要在沙箱中执行它:
new Function("window", "with(window){" + sCode + "}")({});
这两行在执行时将失败,因为“沙箱”中没有“警报”功能
2)现在,您要使用功能公开窗口对象的成员:
new Function("window", "with(window){" + sCode + "}")({
'alert':function(sString){document.title = sString}
});
确实,您可以添加引号转义并进行其他修饰,但是我想这个主意很明确。
该用户JavaScript来自何处?
用户将代码嵌入到页面中,然后从他们的浏览器中调用代码,您无能为力(请参阅Greasemonkey,http://www.greasespot.net/)。这只是浏览器所做的。
但是,如果将脚本存储在数据库中,然后对其进行检索并对其进行eval(),则可以在运行脚本之前对其进行清理。
删除所有窗口的代码示例。和文件。参考资料:
eval(
unsafeUserScript
.replace(/\/\/.+\n|\/\*.*\*\/, '') // Clear all comments
.replace(/\s(window|document)\s*[\;\)\.]/, '') // removes window. or window; or window)
)
这试图防止执行以下操作(未测试):
window.location = 'http://mydomain.com';
var w = window ;
您必须将许多限制应用于不安全的用户脚本。不幸的是,没有可用于JavaScript的“沙盒容器”。
我一直在研究简化的js沙箱,以使用户能够为我的网站构建applet。尽管我在允许DOM访问方面仍然面临一些挑战(parentNode不会让我保持安全= /),但我的方法只是用一些有用/无害的成员重新定义window对象,然后再将eval()用户使用此重新定义的窗口作为默认范围的代码。
我的“核心”代码是这样的……(我没有完全展示出来;)
function Sandbox(parent){
this.scope = {
window: {
alert: function(str){
alert("Overriden Alert: " + str);
},
prompt: function(message, defaultValue){
return prompt("Overriden Prompt:" + message, defaultValue);
},
document: null,
.
.
.
.
}
};
this.execute = function(codestring){
// here some code sanitizing, please
with (this.scope) {
with (window) {
eval(codestring);
}
}
};
}
因此,我可以实例化沙箱并使用其execute()来运行代码。同样,评估代码中所有新声明的变量最终都将绑定到execute()范围,因此不会出现名称冲突或与现有代码混淆的情况。
尽管全局对象仍然可以访问,但是对于沙盒代码仍然不知道的全局对象,必须在Sandbox :: scope对象中将其定义为代理。
希望这对您有用。
您可以将用户代码包装在一个函数中,该函数将禁止对象重新定义为参数-undefined
调用时将是这些:
(function (alert) {
alert ("uh oh!"); // User code
}) ();
当然,聪明的攻击者可以通过检查Javascript DOM并找到一个包含对窗口的引用的不可重写对象来解决此问题。
另一个想法是使用jslint之类的工具扫描用户的代码。确保将其设置为没有预设变量(或:仅您想要的变量),然后如果设置或访问了任何全局变量,则不要使用用户的脚本。再次,可能容易受到DOM的攻击-用户可以使用文字构造的对象可能具有对window对象的隐式引用,可以对其进行访问以逃避沙箱。
文章标签:browser , javascript , sandbox
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!
评论已关闭!