È ansible che JavaScript di Sandbox sia eseguito nel browser?

Mi chiedo se è ansible eseguire sandbox JavaScript nel browser per impedire l’accesso alle funzionalità normalmente disponibili per il codice JavaScript in esecuzione in una pagina HTML.

Ad esempio, supponiamo di voler fornire un’API JavaScript per gli utenti finali che consenta loro di definire i gestori di eventi da eseguire quando si verificano “eventi interessanti”, ma non voglio che quegli utenti accedano alle proprietà e alle funzioni dell’object window . Sono in grado di farlo?

Nel caso più semplice, supponiamo che voglio impedire agli utenti di chiamare l’ alert . Un paio di approcci a cui riesco a pensare sono:

  • Ridefinisci window.alert globalmente. Non penso che questo sarebbe un approccio valido perché altri codici in esecuzione nella pagina (cioè cose non create dagli utenti nei loro gestori di eventi) potrebbero voler usare alert .
  • Invia il codice del gestore eventi al server da elaborare. Non sono sicuro che l’invio del codice al server da elaborare sia l’approccio giusto perché i gestori di eventi devono essere eseguiti nel contesto della pagina.

Forse una soluzione in cui il server elabora la funzione definita dall’utente e quindi genera un callback da eseguire sul client funzionerebbe? Anche se questo approccio funziona, ci sono modi migliori per risolvere questo problema?

Google Caja è un traduttore sorgente-sorgente che “ti consente di mettere in linea HTML e JavaScript non protetti di terze parti nella tua pagina e di essere comunque al sicuro.”

Dai un’occhiata a ADsafe di Douglas Crockford :

ADsafe rende sicuro inserire codice ospite (come pubblicità o widget di script di terze parti) su qualsiasi pagina web. ADsafe definisce un sottoinsieme di JavaScript abbastanza potente da consentire al codice guest di eseguire interazioni di valore, evitando allo stesso tempo danni o intrusioni dannosi o accidentali. Il sottoinsieme ADsafe può essere verificato meccanicamente da strumenti come JSLint in modo che non sia necessario alcun controllo umano per verificare la sicurezza del codice ospite. Il sottoinsieme ADsafe implementa anche buone pratiche di codifica, aumentando la probabilità che il codice guest venga eseguito correttamente.

Puoi vedere un esempio di come usare ADsafe guardando i file template.html e template.js nel repository GitHub del progetto .

Ho creato una libreria sandbox chiamata jsandbox che utilizza i web worker per il codice valutato da sandbox. Ha anche un metodo di input per fornire esplicitamente dati di codice sandbox che altrimenti non sarebbe in grado di ottenere.

Di seguito è riportato un esempio dell’API:

 jsandbox .eval({ code : "x=1;Math.round(Math.pow(input, ++x))", input : 36.565010597564445, callback: function(n) { console.log("number: ", n); // number: 1337 } }).eval({ code : "][];.]\\ (*# ($(! ~", onerror: function(ex) { console.log("syntax error: ", ex); // syntax error: [error object] } }).eval({ code : '"foo"+input', input : "bar", callback: function(str) { console.log("string: ", str); // string: foobar } }).eval({ code : "({q:1, w:2})", callback: function(obj) { console.log("object: ", obj); // object: object q=1 w=2 } }).eval({ code : "[1, 2, 3].concat(input)", input : [4, 5, 6], callback: function(arr) { console.log("array: ", arr); // array: [1, 2, 3, 4, 5, 6] } }).eval({ code : "function x(z){this.y=z;};new x(input)", input : 4, callback: function(x) { console.log("new x: ", x); // new x: object y=4 } }); 

EDIT : Anche se non conosco un modo per sfuggire alla whitelist di seguito, eseguirò il worker da un sandboxing, per ogni evenienza, e vorrei la raccomandazione di js.js di @ gronostaj se l’esaurimento della memoria è un problema.

I lavoratori Web forniscono un modo conveniente per creare un altro contesto di script, che può quindi essere aggressivamente sandbox senza influire sul genitore.

Un’implementazione che usa le promesse:

 function safeEval(untrustedCode) { return new Promise(function (resolve, reject) { var worker = new Worker('eval.js'); worker.onmessage = function (e) { worker.terminate(); resolve(e.data); }; worker.onerror = function (e) { reject(new Error(e.message)); }; worker.postMessage(untrustedCode); setTimeout(function () { worker.terminate(); reject(new Error('The worker timed out.')); }, 1000); }); } 

eval.js (probabilmente vorrai espandere la whitelist):

 (function (global) { 'use strict'; var _postMessage = postMessage; var _addEventListener = addEventListener; (function () { var current = global; var keepProperties = [ // required 'Object', 'Function', 'Infinity', 'NaN', 'undefined', // optional, but trivial to get back 'Array', 'Boolean', 'Number', 'String', 'Symbol', // optional 'Map', 'Math', 'Set', ]; do { Object.getOwnPropertyNames(current).forEach(function (name) { if (keepProperties.indexOf(name) === -1) { delete current[name]; } }); current = Object.getPrototypeOf(current); } while (current !== Object.prototype); })(); _addEventListener('message', function (e) { var f = new Function('', 'return (' + e.data + '\n);'); _postMessage(f()); }); })(this); 

Nessun accesso all’interfaccia utente dai lavoratori, può essere interrotto separatamente, non può bind l’interfaccia utente o lo script in esecuzione e sono standard.

Penso che js.js meriti di essere menzionato qui. È un interprete JavaScript scritto in JavaScript.

È circa 200 volte più lento del JS nativo, ma la sua natura lo rende un perfetto ambiente sandbox. Un altro svantaggio è la sua dimensione – quasi 600 kb, che può essere accettabile per i desktop in alcuni casi, ma non per i dispositivi mobili.

Come menzionato in altre versioni, è sufficiente eseguire il jail del codice in iframe sandbox (senza inviarlo al lato server) e comunicare con i messaggi. Suggerirei di dare un’occhiata a una piccola libreria che ho creato principalmente a causa della necessità di fornire alcune API al codice non attendibile, proprio come descritto nella domanda: c’è un’opportunità per esportare il particolare set di funzioni direttamente nella sandbox dove il codice non affidabile viene eseguito. E c’è anche una demo che esegue il codice inviato da un utente in una sandbox:

http://asvd.github.io/jailed/demos/web/console/

Tutti i fornitori di browser e le specifiche HTML5 stanno lavorando verso una reale proprietà sandbox per consentire iframe in modalità sandbox, ma è ancora limitata alla granularità di iframe.

In generale, nessun livello di espressioni regolari, ecc. Può sanificare in modo sicuro l’utente arbitrario che ha fornito JavaScript mentre degenera al problema dell’arresto: – /

Un modo brutto, ma forse questo funziona per te, ho preso tutti i globals e li ho ridefiniti nel scope sandbox, così ho aggiunto la modalità strict in modo che non possano ottenere l’object globale usando una funzione anonima.

 function construct(constructor, args) { function F() { return constructor.apply(this, args); } F.prototype = constructor.prototype; return new F(); } // Sanboxer function sandboxcode(string, inject) { "use strict"; var globals = []; for (var i in window) { // <--REMOVE THIS CONDITION if (i != "console") // REMOVE THIS CONDITION --> globals.push(i); } globals.push('"use strict";\n'+string); return construct(Function, globals).apply(inject ? inject : {}); } sandboxcode('console.log( this, window, top , self, parent, this["jQuery"], (function(){return this;}()));'); // => Object {} undefined undefined undefined undefined undefined undefined console.log("return of this", sandboxcode('return this;', {window:"sanboxed code"})); // => Object {window: "sanboxed code"} 

https://gist.github.com/alejandrolechuga/9381781

Una versione migliorata del codice sandbox dei web worker @ RyanOHara, in un singolo file (non è necessario eval.js file eval.js extra).

 function safeEval(untrustedCode) { return new Promise(function (resolve, reject) { var blobURL = URL.createObjectURL(new Blob([ "(", function () { var _postMessage = postMessage; var _addEventListener = addEventListener; (function (obj) { "use strict"; var current = obj; var keepProperties = [ // required 'Object', 'Function', 'Infinity', 'NaN', 'undefined', 'caches', 'TEMPORARY', 'PERSISTENT', // optional, but trivial to get back 'Array', 'Boolean', 'Number', 'String', 'Symbol', // optional 'Map', 'Math', 'Set', ]; do { Object.getOwnPropertyNames(current).forEach(function (name) { if (keepProperties.indexOf(name) === -1) { delete current[name]; } }); current = Object.getPrototypeOf(current); } while (current !== Object.prototype); })(this); _addEventListener("message", function (e) { var f = new Function("", "return (" + e.data + "\n);"); _postMessage(f()); }); }.toString(), ")()"], {type: "application/javascript"})); var worker = new Worker(blobURL); URL.revokeObjectURL(blobURL); worker.onmessage = function (evt) { worker.terminate(); resolve(evt.data); }; worker.onerror = function (evt) { reject(new Error(evt.message)); }; worker.postMessage(untrustedCode); setTimeout(function () { worker.terminate(); reject(new Error('The worker timed out.')); }, 1000); }); } 

Provalo:

https://jsfiddle.net/kp0cq6yw/

 var promise = safeEval("1+2+3"); promise.then(function (result) { alert(result); }); 

Dovrebbe produrre 6 (testato in Chrome e Firefox).

Un interprete Javascript indipendente ha maggiori probabilità di fornire una sandbox solida rispetto a una versione ingabbiata dell’implementazione del browser integrato. Ryan ha già menzionato js.js , ma un progetto più aggiornato è JS-Interpreter . I documenti illustrano come esporre varie funzioni all’interprete, ma il suo ambito è molto limitato.

Da dove viene questo JavaScript utente?

Non c’è molto che puoi fare su un utente che incorpora il codice nella tua pagina e poi lo chiama dal browser (vedi Greasemonkey, http://www.greasespot.net/ ). È solo qualcosa che i browser fanno.

Tuttavia, se si memorizza lo script in un database, quindi lo si recupera ed eval (), è ansible pulire lo script prima che venga eseguito.

Esempi di codice che rimuove tutte le windows. e documento. Riferimenti:

  eval( unsafeUserScript .replace(/\/\/.+\n|\/\*.*\*\/, '') // Clear all comments .replace(/\s(window|document)\s*[\;\)\.]/, '') // removes window. or window; or window) ) 

Questo tenta di impedire l’esecuzione di quanto segue (non testato):

 window.location = 'http://mydomain.com'; var w = window ; 

Ci sono molte limitazioni che dovresti applicare allo script utente non sicuro. Sfortunatamente, non esiste un ‘contenitore sandbox’ disponibile per JavaScript.

1) Supponiamo di avere un codice da eseguire:

 var sCode = "alert(document)"; 

Ora, supponiamo di volerlo eseguire in una sandbox:

 new Function("window", "with(window){" + sCode + "}")({}); 

Queste due righe non verranno eseguite, poiché la funzione “avviso” non è disponibile dalla “sandbox”

2) E ora vuoi esporre un membro di window object con la tua funzionalità:

 new Function("window", "with(window){" + sCode + "}")({ 'alert':function(sString){document.title = sString} }); 

In effetti puoi aggiungere delle virgolette che escaping e fare altre lucidature, ma immagino che l’idea sia chiara.

Puoi avvolgere il codice dell’utente in una funzione che ridefinisce gli oggetti vietati come parametri – questi sarebbero quindi undefined quando chiamati:

 (function (alert) { alert ("uh oh!"); // User code }) (); 

Ovviamente, gli hacker intelligenti possono aggirarlo ispezionando il DOM Javascript e individuando un object non sottoposto a override che contiene un riferimento alla finestra.


Un’altra idea è la scansione del codice dell’utente utilizzando uno strumento come jslint . Assicurati che sia impostato per non avere variabili preimpostate (o: solo le variabili che vuoi), e quindi se sono impostati o accessibili globali, non lasciare che lo script dell’utente venga usato. Anche in questo caso, potrebbe essere vulnerabile al cammino del DOM: gli oggetti che l’utente può build utilizzando i letterali potrebbero avere riferimenti impliciti all’object della finestra a cui è ansible accedere per uscire dalla sandbox.

Ho lavorato su una js sandbox semplicistica per consentire agli utenti di creare applet per il mio sito. Sebbene io abbia ancora delle difficoltà nel consentire l’accesso al DOM (parentNode non mi permette di mantenere le cose sicure = /), il mio approccio era solo ridefinire l’object window con alcuni dei suoi membri utili / innocui, e quindi eval () l’utente codice con questa finestra ridefinita come ambito predefinito.

Il mio codice “core” va così … (non lo mostro interamente;)

 function Sandbox(parent){ this.scope = { window: { alert: function(str){ alert("Overriden Alert: " + str); }, prompt: function(message, defaultValue){ return prompt("Overriden Prompt:" + message, defaultValue); }, document: null, . . . . } }; this.execute = function(codestring){ // here some code sanitizing, please with (this.scope) { with (window) { eval(codestring); } } }; } 

Quindi, posso istanziare un Sandbox e usare il suo execute () per ottenere il codice in esecuzione. Inoltre, tutte le nuove variabili dichiarate all’interno del codice eval’d saranno in ultima analisi legate all’ambito execute (), quindi non ci saranno conflitti di nomi o problemi con il codice esistente.

Sebbene gli oggetti globali siano ancora accessibili, quelli che dovrebbero rimanere sconosciuti al codice in modalità sandbox devono essere definiti come proxy nell’object Sandbox :: scope.

Spero che questo funzioni per te.