Создание WebWorkers безопасной среды



в стремлении иметь интерфейс, способный запускать произвольный код javascript внутри браузера, не имея дыры в безопасности размером с типичную шутку yo-mama, Esailija предложила использовать Веб-Работников. Они работают в полу-изолированной среде (без доступа DOM и уже внутри браузера) и могут быть убиты, поэтому пользователь не может поместить их в бесконечный цикл.



вот пример, который он привел: http://tuohiniemi.fi / ~runeli/petka/workertest.html (Откройте консоль)



jsfiddle (только Google chrome)



теперь это кажется хорошим решением; однако, является ли оно полным (или приближается к полному)? Есть ли что-то очевидное упускаю?



все это (как это подключено к боту) можно найти на github:работник, оценщика



main:



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

2 ответов:

текущий код (перечисленный ниже) уже некоторое время используется в чате StackOverflow javascript и до сих пор самая сложная проблема была Array(5000000000).join("adasdadadasd") мгновенно сбой какой вкладки браузера для меня, когда я запускал бот-исполнитель кода. Monkeypatching Array.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) );
    };

})();

код в настоящее время (2014-11-07) показан в вопросе, несмотря на якобы запрет доступа к XMLHttpRequest (так как это не белый список), по-прежнему позволяет коду получить к нему доступ.

если я помещаю код в вопрос (или принятый ответ) на веб-странице и рабочем комбо и выполнить следующий код на Chrome 38:

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

результат:

function XMLHttpRequest() { [native code] } 

однако он не работает в FF. Баг в Chrome?

еще одна вещь, которую я нашел, но который, кажется, не ведет очень далеко вниз отверстие rabit восстанавливает 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, который может быть внесен в черный список (удалив его из белого списка), но this и global также работать. Я обнаружил, что попытки внести в черный список global сбой на FF и Chrome: рабочий умирает с помощью ошибка.

Примечание: Chrome отказывается от черного списка Intl поэтому он должен быть добавлен в белый список для запуска кода вообще.

Comments

    Ничего не найдено.