使WebWorkers成为一个安全的环境

为了让浏览器能够运行一个任意的JavaScript代码,并且没有一个典型的妈妈玩笑的安全漏洞, Esailijabuild议使用Web Workers 。 他们运行在一个半沙盒环境(没有DOM访问权限,已经在浏览器中),可以被杀死,因此用户不能把它们放在一个无限循环中。

这是他提出的例子: http : //tuohiniemi.fi/~runeli/petka/workertest.html (打开你的控制台)

jsfiddle (仅限Google Chrome)

现在,这似乎是一个很好的解决scheme; 然而,这是一个完整的(或接近完整的)吗? 有什么明显的缺失?

整个事情(因为它连接到一个机器人)可以在github上find: 工作者 , 评估者

主要:

workercode = "worker.js"; function makeWorkerExecuteSomeCode( code, callback ) { var timeout; code = code + ""; var worker = new Worker( workercode ); worker.addEventListener( "message", function(event) { clearTimeout(timeout); callback( event.data ); }); worker.postMessage({ code: code }); timeout = window.setTimeout( function() { callback( "Maximum execution time exceeded" ); worker.terminate(); }, 1000 ); } makeWorkerExecuteSomeCode( '5 + 5', function(answer){ console.log( answer ); }); makeWorkerExecuteSomeCode( 'while(true);', function(answer){ console.log( answer ); }); var kertoma = 'function kertoma(n){return n === 1 ? 1 : n * kertoma(n-1)}; kertoma(15);'; makeWorkerExecuteSomeCode( kertoma, function(answer){ console.log( answer ); }); 

工人:

 var global = this; /* Could possibly create some helper functions here so they are always available when executing code in chat?*/ /* Most extra functions could be possibly unsafe */ var wl = { "self": 1, "onmessage": 1, "postMessage": 1, "global": 1, "wl": 1, "eval": 1, "Array": 1, "Boolean": 1, "Date": 1, "Function": 1, "Number" : 1, "Object": 1, "RegExp": 1, "String": 1, "Error": 1, "EvalError": 1, "RangeError": 1, "ReferenceError": 1, "SyntaxError": 1, "TypeError": 1, "URIError": 1, "decodeURI": 1, "decodeURIComponent": 1, "encodeURI": 1, "encodeURIComponent": 1, "isFinite": 1, "isNaN": 1, "parseFloat": 1, "parseInt": 1, "Infinity": 1, "JSON": 1, "Math": 1, "NaN": 1, "undefined": 1 }; Object.getOwnPropertyNames( global ).forEach( function( prop ) { if( !wl.hasOwnProperty( prop ) ) { Object.defineProperty( global, prop, { get : function() { throw new Error( "Security Exception: cannot access "+prop); return 1; }, configurable : false }); } }); Object.getOwnPropertyNames( global.__proto__ ).forEach( function( prop ) { if( !wl.hasOwnProperty( prop ) ) { Object.defineProperty( global.__proto__, prop, { get : function() { throw new Error( "Security Exception: cannot access "+prop); return 1; }, configurable : false }); } }); onmessage = function( event ) { "use strict"; var code = event.data.code; var result; try { result = eval( '"use strict";\n'+code ); } catch(e){ result = e.toString(); } postMessage( "(" + typeof result + ")" + " " + result ); }; 

当前的代码(下面列出)已经在Stackoverflow JavaScript聊天室中使用了一段时间,到目前为止最棘手的问题是Array(5000000000).join("adasdadadasd")在运行时立即崩溃了一些浏览器选项卡代码执行者机器人。 Monkeypatching Array.prototype.join似乎已经解决了这个问题,50毫秒的最大执行时间已经为任何其他企图杀猪内存或崩溃的浏览器。

 var global = this; /* Could possibly create some helper functions here so they are always available when executing code in chat?*/ /* Most extra functions could be possibly unsafe */ var wl = { "self": 1, "onmessage": 1, "postMessage": 1, "global": 1, "wl": 1, "eval": 1, "Array": 1, "Boolean": 1, "Date": 1, "Function": 1, "Number" : 1, "Object": 1, "RegExp": 1, "String": 1, "Error": 1, "EvalError": 1, "RangeError": 1, "ReferenceError": 1, "SyntaxError": 1, "TypeError": 1, "URIError": 1, "decodeURI": 1, "decodeURIComponent": 1, "encodeURI": 1, "encodeURIComponent": 1, "isFinite": 1, "isNaN": 1, "parseFloat": 1, "parseInt": 1, "Infinity": 1, "JSON": 1, "Math": 1, "NaN": 1, "undefined": 1 }; Object.getOwnPropertyNames( global ).forEach( function( prop ) { if( !wl.hasOwnProperty( prop ) ) { Object.defineProperty( global, prop, { get : function() { throw "Security Exception: cannot access "+prop; return 1; }, configurable : false }); } }); Object.getOwnPropertyNames( global.__proto__ ).forEach( function( prop ) { if( !wl.hasOwnProperty( prop ) ) { Object.defineProperty( global.__proto__, prop, { get : function() { throw "Security Exception: cannot access "+prop; return 1; }, configurable : false }); } }); Object.defineProperty( Array.prototype, "join", { writable: false, configurable: false, enumerable: false, value: function(old){ return function(arg){ if( this.length > 500 || (arg && arg.length > 500 ) ) { throw "Exception: too many items"; } return old.apply( this, arguments ); }; }(Array.prototype.join) }); (function(){ var cvalues = []; var console = { log: function(){ cvalues = cvalues.concat( [].slice.call( arguments ) ); } }; function objToResult( obj ) { var result = obj; switch( typeof result ) { case "string": return '"' + result + '"'; break; case "number": case "boolean": case "undefined": case "null": case "function": return result + ""; break; case "object": if( !result ) { return "null"; } else if( result.constructor === Object || result.constructor === Array ) { var type = ({}).toString.call( result ); var stringified; try { stringified = JSON.stringify(result); } catch(e) { return ""+e; } return type + " " + stringified; } else { return ({}).toString.call( result ); } break; } } onmessage = function( event ) { "use strict"; var code = event.data.code; var result; try { result = eval( '"use strict";\n'+code ); } catch(e) { postMessage( e.toString() ); return; } result = objToResult( result ); if( cvalues && cvalues.length ) { result = result + cvalues.map( function( value, index ) { return "Console log "+(index+1)+":" + objToResult(value); }).join(" "); } postMessage( (""+result).substr(0,400) ); }; })(); 

尽pipe表面上不允许访问XMLHttpRequest (因为它没有被列入白名单),但是代码当前(2014-11-07)仍然允许代码访问它。

如果我把问题(或接受的答案)中的代码放在网页和工作人员组合中,并在Chrome 38上执行以下代码:

 makeWorkerExecuteSomeCode('event.target.XMLHttpRequest', function (answer) { console.log( answer ); }); 

结果是:

 function XMLHttpRequest() { [native code] } 

但是它在FF中不起作用。 Chrome中的错误?

我发现的另一件事情,但似乎没有导致非常深入的漏洞是恢复console.log 。 这适用于FF 31,但不适用于Chrome 38:

 makeWorkerExecuteSomeCode( 'var c = self.__proto__.__proto__.__lookupGetter__("console").call(self); c.log("FOO");', function (answer) { console.log(answer) }); 

这将logging"FOO"到控制台,而不通过Web工作者提供的假console.log 。 上面的代码使用self ,可以被列入黑名单(从白名单中删除),但这和global也工作。 我发现在FF和Chrome上尝试黑名单global失败:工作人员死于一个错误。

请注意:Chrome拒绝将Intl列入黑名单,因此必须将其添加到白名单中才能运行代码。