2012-05-18 5 views
59

In una ricerca per avere un'interfaccia in grado di eseguire codice javascript arbitrario all'interno del browser, senza avere un buco di sicurezza delle dimensioni di un tipico scherzo yo-mama, Esailija proposto utilizzando Web Workers. Funzionano in un ambiente semi-sandbox (nessun accesso DOM e già all'interno del browser) e possono essere uccisi in modo che l'utente non possa inserirli in un ciclo infinito.Rendere un WebWorkers un ambiente sicuro

Ecco l'esempio ha portato fino: http://tuohiniemi.fi/~runeli/petka/workertest.html (aprire la console)

jsfiddle (Google Chrome solo)

Ora, questa sembra una buona soluzione; tuttavia, è completo (o si avvicina completo)? C'è qualcosa di evidente mancante?

L'intera cosa (come è collegato a un bot) si possono trovare su github: worker, evaluator

principale:

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

lavoratore:

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

Non sarebbero ancora in grado di inviare richieste AJAX? – SLaks

+0

@SLaks L'operatore ha XHR impostato su 'null' – Esailija

+0

@SLaks È possibile rimuovere manualmente un gruppo di proprietà non protette, come mostrato nell'esempio. – Zirak

risposta

30

Il codice attuale (elencato di seguito) è stato utilizzato nella chat room di Stackoverflow per un po 'di tempo e finora il problema più difficile era Array(5000000000).join("adasdadadasd") bloccava istantaneamente alcune schede del browser quando eseguivo il bot del codice executor. Monkeypatching Array.prototype.join sembra aver risolto questo problema e il massimo tempo di esecuzione di di 50 ms ha funzionato per qualsiasi altro tentativo di memorizzare memoria o arrestare il browser.

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

Congrats :) Questo è il più vicino possibile Immagino –

+0

Perché le funzioni Math e parse * sono disabilitate? – Domi

+0

@Domi perché pensi che siano disabilitati – Esailija

5

Il codice corrente (2014/11/07) mostrato in questione nonostante non consentire apparentemente accesso a XMLHttpRequest (poiché non è whitelist), consente ancora il codice per accedervi.

Se metto il codice in questione (o la risposta accettata) in una pagina web e dei lavoratori combo ed eseguire il seguente codice su Chrome 38:

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

Il risultato è:

function XMLHttpRequest() { [native code] } 

Tuttavia non funziona in FF. Bug in Chrome?

Un'altra cosa che ho trovato ma che non sembra portare molto in fondo alla rabbia buca sta ripristinando console.log. Questo funziona su 31 FF, ma non Chrome 38:

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

Ciò accedere "FOO" alla console senza passare attraverso il falso console.log che il lavoratore web fornisce. Il codice sopra riportato utilizza self, che può essere inserito nella lista nera (rimuovendolo dalla lista bianca) ma funzionano anche this e global. Ho trovato che i tentativi di blacklist global falliscono su FF e Chrome: l'operatore muore con un errore.

Nota: Chrome rifiuta la lista nera Intl quindi deve essere aggiunto alla lista bianca affinché il codice possa essere eseguito.

+0

Ottimo punto per quanto riguarda la buca in Chrome 38, ma è piuttosto facile da riempire: basta mettere una chiusura attorno a 'try/catch' in' onmessage' e ridefinire 'event' con' var event; 'all'interno della chiusura. – heinob