有没有可能在浏览器中运行沙箱 JavaScript?

我想知道是否有可能在浏览器中运行沙箱 JavaScript 来阻止访问 HTML 页面中运行的 JavaScript 代码通常可用的特性。

例如,假设我想为最终用户提供一个 JavaScript API,让他们定义在“有趣的事件”发生时运行的事件处理程序,但是我不希望这些用户访问 window对象的属性和函数。我能做到吗?

在最简单的情况下,假设我想防止用户调用 alert。我能想到的几种方法是:

  • 全局重新定义 window.alert。我不认为这是一种有效的方法,因为页面中运行的其他代码(例如,未由用户在其事件处理程序中编写的内容)可能希望使用 alert
  • 将事件处理程序代码发送到要处理的服务器。我不确定将代码发送到服务器进行处理是否是正确的方法,因为事件处理程序需要在页面的上下文中运行。

也许一个解决方案,服务器处理用户定义的函数,然后生成一个回调,在客户端上执行?即使这种方法有效,是否有更好的方法来解决这个问题?

62598 次浏览

您可以将用户的代码封装在一个函数中,该函数将禁用对象重新定义为参数——当调用时,这些参数将是 undefined:

(function (alert) {


alert ("uh oh!"); // User code


}) ();

当然,聪明的攻击者可以通过检查 JavaScriptDOM 并找到一个包含对窗口的引用的非重写对象来绕过这个问题。


另一个想法是使用像 JSLint这样的工具扫描用户的代码。确保它被设置为没有预设变量(或: 只有你想要的变量) ,然后如果有任何全局设置或访问不让用户的脚本被使用。同样,它可能易于遍历 DOM ——用户可以使用文字构造的对象可能有对窗口对象的隐式引用,可以访问这些对象以避开沙箱。

这个用户 JavaScript 代码来自哪里?

对于用户将代码嵌入到页面中,然后从他们的浏览器调用该代码,您可以做的事情不多(请参见 油猴子)。浏览器就是这样。

但是,如果将脚本存储在数据库中,然后检索它并 eval ()它,那么可以在运行脚本之前清除它。

删除所有窗口和文档引用的代码示例:

 eval(
unsafeUserScript
.replace(/\/\/.+\n|\/\*.*\*\/, '') // Clear all comments
.replace(/\s(window|document)\s*[\;\)\.]/, '') // Removes window. Or window; or window)
)

它试图阻止执行下列操作(未经测试) :

window.location = 'http://example.com';
var w = window;

您必须对不安全的用户脚本应用很多限制。不幸的是,没有任何可用于 JavaScript 的“沙箱容器”。

所有的浏览器厂商和 HTML5规范都致力于实现一个真正的沙箱属性,以支持沙箱 iframe ——但它仍然局限于 iframe 粒度。

一般来说,没有程度的正则表达式等可以安全地清除任意用户提供的 JavaScript,因为它退化为停止问题:-/

Google Caja 是一个源到源的翻译器,它“允许你在页面中内联不受信任的第三方 HTML 和 JavaScript,并且仍然是安全的。”

  1. 假设您有代码要执行:

     var sCode = "alert(document)";
    

    现在,假设您想在沙箱中执行它:

     new Function("window", "with(window){" + sCode + "}")({});
    

    这两行在执行时将会失败,因为“ alert”函数在“ sandbox”中不可用

  2. 现在,您希望使用您的功能公开窗口对象的一个成员:

     new Function("window", "with(window){" + sCode + "}")({
    'alert':function(sString){document.title = sString}
    });
    

事实上,你可以添加报价逃逸,使其他抛光,但我想这个想法是明确的。

看看 道格拉斯·克罗克福特的 ADsafe:

ADsafe 可以安全地在任何网页上放置来宾代码(比如第三方脚本广告或小部件)。ADsafe 定义了 JavaScript 的一个子集,该子集强大到足以允许客户代码执行有价值的交互,同时防止恶意或意外的损害或入侵。ADsafe 子集可以通过诸如 JSLint 之类的工具进行机械验证,这样就不需要人工检查来检查来宾代码的安全性。ADsafe 子集还强制执行良好的编码实践,增加了来宾代码正确运行的可能性。

通过查看 项目的 GitHub 存储库中的 template.htmltemplate.js文件,您可以看到如何使用 ADsafe 的示例。

我一直在开发一个简单的 JavaScript 沙箱,用于让用户为我的站点构建小程序。尽管在允许 DOM 访问方面我仍然面临一些挑战(ParentNode 不让我保证安全 =/) ,我的方法只是用一些有用的/无害的成员重新定义窗口对象,然后 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);
}
}
};
}

因此,我可以实例化一个 Sandbox 并使用它的 执行()函数来运行代码。而且,eval’d 代码中的所有新声明的变量最终都将绑定到 execute ()作用域,因此不会出现名称冲突或与现有代码混淆的情况。

虽然全局对象仍然是可访问的,但是那些沙箱代码仍然不知道的对象必须在 Sandbox: : scope 对象中定义为代理。

我创建了一个名为 Jsandbox的沙箱库,它使用 web worker 对已评估的代码进行沙箱处理。它还有一个输入方法,用于显式地给出沙箱代码数据,否则无法获得这些数据。

下面是 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
}
});

虽然很难看,但也许这对你有用:

我获取了所有的全局变量,并在沙箱范围内重新定义了它们,同时我还添加了严格模式,这样它们就不能使用匿名函数获取全局对象。

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

我认为 Js.js在这里值得一提,它是一个用 JavaScript 编写的 JavaScript 解释器。

它比原生 JavaScript 慢200倍,但是它的特性使它成为一个完美的沙盒环境。另一个缺点是它的大小——将近600KB,这在某些情况下对桌面可能是可以接受的,但对移动设备就不行了。

正如在其他响应中提到的,将代码禁锢在沙箱 iframe 中(不将其发送到服务器端)并与消息进行通信就足够了。

我建议看看我创建的 一个小图书馆,主要是因为需要为不受信任的代码提供一些 API,就像问题中描述的那样: 有机会将特定的函数集直接导出到运行不受信任代码的沙箱中。还有一个演示程序可以执行用户在沙箱中提交的代码:

Http://asvd.github.io/jailed/demos/web/console/

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 中测试过)。

一个独立的 JavaScript 解释器比内置浏览器实现的笼状版本更有可能产生一个健壮的沙箱。

瑞安有 已经说过了 Js.js,但一个更新的项目是 JS 翻译文件介绍了如何向解释器公开各种函数,但是它的作用域非常有限。

截至2019年,Vm2似乎是运行 JavaScript在 Node.js 中的最流行和最定期更新的解决方案。我不知道什么前端解决方案。

使用 NISP,您将能够进行沙箱评估。

虽然您所编写的表达式不完全是 JavaScript 代码,但是您将编写 S 表达式。对于不需要大量编程的简单 领域特定语言来说,它是理想的。