2012-05-18 11 views
59

W dążeniu do posiadania interfejsu zdolnego do uruchamiania dowolnego kodu javascript wewnątrz przeglądarki, bez dziury w bezpieczeństwie wielkości typowego żartu jo-mama, Esailija proponowanego przy użyciu Web Workers. Działają w środowisku pół-piaskownicy (brak dostępu do DOM i już wewnątrz przeglądarki) i mogą zostać zabite, aby użytkownik nie mógł umieścić ich w nieskończonej pętli.Uczynienie WebWorkers bezpiecznym środowiskiem

Oto przykład on wychowany: http://tuohiniemi.fi/~runeli/petka/workertest.html (otwórz konsolę)

jsfiddle (tylko Google chrome)

Teraz wydaje się to dobrym rozwiązaniem; jednak czy jest to kompletny (lub zbliża się kompletny)? Czy brakuje czegoś oczywistego?

Cała rzecz (jak to podłączyć do bot) można znaleźć na github: worker, evaluator

główny:

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

pracownika:

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

Czy nadal nie mogą wysyłać żądań AJAX? – SLaks

+0

@SLaks Pracownik ma XHR ustawiony na 'null' – Esailija

+0

@SLaks Możesz samodzielnie usunąć kilka niepewnych właściwości, jak pokazano w przykładzie. – Zirak

Odpowiedz

30

aktualny kod (Wymienione poniżej) jest teraz w użyciu w pokoju czatowym Stackoverflow na chwilę i jak dotąd najtrudniejszym problemem był Array(5000000000).join("adasdadadasd") powodując natychmiastowe zawieszenie się niektórych kart przeglądarki przeglądarki , gdy uruchamiałem bot executora kodu. Wydaje się, że Monkeypatching Array.prototype.join naprawił ten błąd, a maksymalny czas wykonania wynoszący 50ms zadziałał w przypadku każdej innej próby przejęcia pamięci lub awarii przeglądarki.

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

})(); 
+0

Gratulacje :) To jest tak blisko, jak to się wydaje –

+0

Dlaczego funkcje matematyczne i parsowanie * są wyłączone? – Domi

+0

@Domi dlaczego uważasz, że są one wyłączone? – Esailija

5

Kod aktualnie (07.11.2014) przedstawionego w pytaniu mimo pozornie zabronienie dostępu do XMLHttpRequest (ponieważ nie jest na białej liście), nadal pozwala kod dostępu do niego.

Jeśli mogę umieścić kod w pytaniu (lub zaakceptowane odpowiedź) na stronie internetowej i pracownika combo i wykonać następujący kod w Chrome 38:

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

Wynikiem jest:

function XMLHttpRequest() { [native code] } 

Jednak nie działa w trybie FF. Błąd w Chrome?

Inną rzeczą, którą znalazłem, ale która nie wydaje się prowadzić daleko w dole rabitowej dziury, jest przywrócenie console.log. To działa na FF 31 ale nie Chrome 38:

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

byłoby to log "FOO" do konsoli bez przechodzenia przez fałszywą console.log że pracownik internetowy zapewnia. Powyższy kod używa numeru self, który można umieścić na czarnej liście (usuwając go z białej listy), ale działa również this i global. Zauważyłem, że próba zablokowania na czarnej liście global kończy się niepowodzeniem na FF i Chrome: pracownik umiera z powodu błędu.

Uwaga: Chrome odmawia utworzenia czarnej listy Intl, więc musi zostać dodany do białej listy, aby kod mógł w ogóle działać.

+0

Bardzo dobry punkt dotyczący otworu w Chrome 38, ale jest to raczej łatwe do wypełnienia: Wystarczy zamknąć zamknięcie wokół 'try/catch' w' onmessage' i ponownie zdefiniować 'zdarzenie' z' var event; 'wewnątrz zamknięcia. – heinob

Powiązane problemy