使WebWorkers成为安全的环境


68

为了寻求一个能够在浏览器中运行任意javascript代码的界面,而又没有安全漏洞的大小,Esailija建议使用Web Workers。它们在半沙盒环境(没有DOM访问并且已经在浏览器内部)中运行,并且可以被杀死,因此用户无法将它们置于无限循环中。

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

jsfiddle(仅适用于Google chrome)

现在,这似乎是一个很好的解决方案。但是,它是完整的(或接近完整的)吗?有什么明显的遗漏吗?

可以在github上找到整个内容(因为它已经连接到了bot):workerevaluator

主要:

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 );
};

他们仍然无法发送AJAX请求吗?
SLaks 2012年

@SLaks工人已将XHR设置为null
Esailija

2
看来,如果您delete是本机/宿主对象,则它将还原为原始状态。"delete XMLHttpRequest; XMLHttpRequest;"将返回原始的XMLHttpRequest对象。必须解决这个问题:/
Esailija 2012年

1
您有不安全的功能黑名单。看起来不太好。如果新标准或浏览器定义了新的不安全功能怎么办?
zch 2012年

1
作为一个偏执狂,我将添加某种运行时测试,即您的方法实际上阻止了操作系统上的某些内容。否则,看起来还可以,但我不是专家。
zch 2012年

Answers:


37

当前的代码(在下面列出)已经在Stackoverflow javascript聊天室中使用了一段时间,到目前为止,最棘手的问题是Array(5000000000).join("adasdadadasd")当我运行代码执行器bot时立即崩溃了一些浏览器选项卡。MonkeypatchingArray.prototype.join似乎已解决了该问题,并且任何其他尝试占用内存或使浏览器崩溃的最大工作时间为50ms。

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) );
    };

})();

恭喜:)我猜这已经接近了
Benjamin Gruenbaum 2013年

为什么禁用数学和解析功能?
米2014年

1
@Domi我认为您误会了-这是白名单方法。未列出的所有内容均被禁止,而仅允许列出的内容。白名单更好,因为在实施新的API时不需要更新。
Esailija 2014年

2
您如何知道将来的API?:-)但是我现在检查了一下,Object.defineProperty会抛出并阻止设置onmessage处理程序。
Bergi 2014年

1
@Domi * Timeout和* Interval函数在jsfiddle.net/WuhrP/2中列出(我正在使用版本33.0.1750.152)
Esailija 2014年

5

尽管表面上不允许访问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) });

这将登录"FOO"到控制台,而不会通过console.log网络工作者提供的伪造品。上述用途的代码self,其可以(通过从白名单中移除)被列入黑名单,但thisglobal也工作。我发现global在FF和Chrome上尝试将其列入黑名单失败:该工作人员死于错误。

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


非常令人担忧的好点在Chrome 38洞,但是这是比较容易填补:只要把封闭围绕try/catchonmessage并重新定义eventvar event;接线盒中。
heinob 2014年

那么,有什么真正的解决方案吗?
格雷戈里·玛格沙克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.