沙盒JavaScript运行在浏览器中吗?
我想知道是否可以在浏览器中运行沙盒JavaScript,以防止访问通常可用于在HTML页面中运行的JavaScript代码的function。
例如,假设我想为最终用户提供一个JavaScript API,让他们定义要在发生“有趣事件”时运行的事件处理程序,但我不希望这些用户访问window
对象的属性和function。 我能做到吗?
在最简单的情况下,假设我想阻止用户调用alert
。 我能想到的几个方法是:
- 全局重新定义
window.alert
。 我不认为这将是一个有效的方法,因为在页面中运行的其他代码(即没有用户在他们的事件处理程序中创build的东西)可能要使用alert
。 - 将事件处理程序代码发送到服务器进行处理。 我不确定将代码发送到服务器进行处理是正确的方法,因为事件处理程序需要在页面的上下文中运行。
也许服务器处理用户定义的函数,然后生成一个callback在客户端上执行的解决scheme将工作? 即使这种方法有效,有没有更好的方法来解决这个问题?
Google Caja是一个源代码到源代码的翻译器,可以让您将不受信任的第三方HTML和JavaScript内联到您的页面中,并且仍然安全。
看看Douglas Crockford的ADsafe :
ADsafe可以安全地在任何网页上放置客人代码(如第三方脚本广告或小部件)。 ADsafe定义了一个足够强大的JavaScript子集,允许客人代码执行有价值的交互,同时防止恶意或意外的破坏或入侵。 ADsafe子集可以通过JSLint等工具进行机械validation,因此不需要人为检查来检查客人代码的安全性。 ADsafe子集还实施了良好的编码实践,增加了客人代码正确运行的可能性。
您可以通过查看项目的GitHub存储库中的template.html
和template.js
文件来查看如何使用ADsafe的示例。
我创build了一个名为jsandbox的沙箱库,它使用Web工作人员来评估沙箱评估代码。 它也有一个input方法,用于显式地给出沙箱代码数据,否则将无法获得。
以下是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>
的工人,以防万一,如果内存耗尽是一个问题,第二个@ gronostaj的js.jsbuild议。
Web工作者提供了一种方便的方法来创build另一个脚本上下文,然后可以在不影响父代的情况下积极地进行沙盒操作。
使用承诺的实现:
function safeEval(untrustedCode) { return new Promise(function (resolve, reject) { var worker = new Worker('eval.js'); worker.onmessage = function (e) { worker.terminate(); resolve(e.data); }; worker.onerror = function (e) { reject(new Error(e.message)); }; worker.postMessage(untrustedCode); setTimeout(function () { worker.terminate(); reject(new Error('The worker timed out.')); }, 1000); }); }
eval.js
(你可能想要扩展白名单):
(function (global) { 'use strict'; var _postMessage = postMessage; var _addEventListener = addEventListener; (function () { var current = global; var keepProperties = [ // required 'Object', 'Function', 'Infinity', 'NaN', 'undefined', // 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); })(); _addEventListener('message', function (e) { var f = new Function('', 'return (' + e.data + '\n);'); _postMessage(f()); }); })(this);
没有来自工人的UI访问,可以单独终止,不能捆绑UI或运行脚本,并且是标准的。
我认为js.js在这里值得一提。 这是一个用JavaScript编写的JavaScript解释器。
它比原生JS慢大约200倍,但其本质使其成为一个完美的沙箱环境。 另一个缺点是它的大小 – 几乎是600kb,这在某些情况下可能是台式机可以接受的,但不适用于移动设备。
正如其他响应中提到的那样,将沙盒iframe中的代码(不发送给服务器端)和监听消息进行通信就足够了。 我build议看看我创build的一个小型图书馆,主要是因为需要为不受信任的代码提供一些API,就像问题中所描述的那样:有一个机会将特定的函数集导出到沙箱中不受信任的代码运行。 还有一个演示程序,它执行用户在沙箱中提交的代码:
所有的浏览器供应商和HTML5规范正在努力实现沙箱属性,以允许沙箱化的iframe – 但它仍然限于iframe的粒度。
一般来说,没有正则expression式的程度等可以安全地消毒任意用户提供的JavaScript,因为它退化为暂停问题: – /
一个丑陋的方式,但也许这对你有用,我把所有的全局variables,并在沙箱范围内重新定义它们,以及我添加了严格模式,所以他们不能使用匿名函数获取全局对象。
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"}
@ RyanOHara的web工作者沙箱代码的改进版本,在一个单一的文件 (没有额外的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); }); }
testing它:
https://jsfiddle.net/kp0cq6yw/
var promise = safeEval("1+2+3"); promise.then(function (result) { alert(result); });
它应该输出6
(在Chrome和Firefox中testing)。
一个独立的Javascript解释器比内置浏览器实现的笼子版本更可能产生一个强大的沙盒。 Ryan 已经提到了 js.js ,但是一个更新的项目是JS-Interpreter 。 文档涵盖了如何向口译员展示各种function,但其范围是非常有限的。
这个用户的JavaScript来自哪里?
对于用户将代码embedded到页面中,然后从浏览器调用代码,可以做的事情不多(请参见Greasemonkey, http://www.greasespot.net/ )。 这只是浏览器所做的一切。
但是,如果将脚本存储在数据库中,则检索该脚本并对其进行评估(eval()),然后可以在脚本运行之前清理脚本。
删除所有窗口的代码示例。 和文件。 引用:
eval( unsafeUserScript .replace(/\/\/.+\n|\/\*.*\*\/, '') // Clear all comments .replace(/\s(window|document)\s*[\;\)\.]/, '') // removes window. or window; or window) )
这试图阻止执行以下(未testing):
window.location = 'http://mydomain.com'; var w = window ;
有很多限制,你将不得不适用于不安全的用户脚本。 不幸的是,没有“沙盒容器”可用于JavaScript。
1)假设你有一个代码来执行:
var sCode = "alert(document)";
现在,假设你想在沙箱中执行它:
new Function("window", "with(window){" + sCode + "}")({});
这两行执行时会失败,因为“警告”function不能从“沙盒”
2)现在你想用你的function公开一个窗口对象的成员:
new Function("window", "with(window){" + sCode + "}")({ 'alert':function(sString){document.title = sString} });
事实上,你可以添加引号逃脱,并进行其他抛光,但我想这个想法是明确的。
您可以将用户的代码封装在一个函数中,该函数将禁止的对象重新定义为参数 – 调用时这些函数将会是undefined
:
(function (alert) { alert ("uh oh!"); // User code }) ();
当然,聪明的攻击者可以通过检查Javascript DOM并find包含对窗口的引用的未覆盖的对象来解决此问题。
另一个想法是使用像jslint这样的工具来扫描用户的代码。 确保它被设置为没有预设variables(或者:只有你想要的variables),然后如果设置或访问了全局variables,不要让用户的脚本被使用。 同样,可能容易走路的DOM – 用户可以使用文字构造对象可能隐式引用窗口对象,可以访问以逃脱沙箱。
我一直在做一个简单的js沙箱,让用户为我的网站构build小程序。 虽然我仍然在允许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并使用其execute()来运行代码。 另外,eval代码中的所有新声明的variables最终都会绑定到execute()作用域,所以不会有冲突的名称或与现有的代码混淆。
虽然全局对象仍然是可访问的,但是对于沙盒代码,这些对象应该是未知的,必须在Sandbox :: scope对象中定义为代理。
希望这对你有用。