È possibile eseguire il sandbox JavaScript in esecuzione nel browser?


142

Mi chiedo se è possibile eseguire il sandbox di JavaScript in esecuzione 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 agli utenti finali per consentire loro di definire i gestori di eventi da eseguire quando si verificano "eventi interessanti", ma non desidero che tali utenti accedano alle proprietà e alle funzioni windowdell'oggetto. Sono in grado di farlo?

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

  • Ridefinisci a window.alertlivello globale. Non penso che questo sarebbe un approccio valido perché potrebbe essere utile utilizzare altro codice in esecuzione nella pagina (ovvero elementi non creati dagli utenti nei gestori di eventi) alert.
  • Invia il codice del gestore eventi al server per l'elaborazione. Non sono sicuro che l'invio del codice al server per l'elaborazione 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?

Risposte:


54

Google Caja è un traduttore da fonte a fonte che "ti consente di mettere in linea HTML e JavaScript di terze parti non attendibili nella tua pagina ed essere comunque sicuri".


5
Un rapido test mostra che Caja non è in grado di proteggere il browser dagli attacchi della CPU come while (1) {}--- si blocca e basta. Allo stesso modo a=[]; while (1) { a=[a,a]; }.
David dato il

5
Sì, la negazione del servizio non rientra nell'ambito di applicazione: code.google.com/p/google-caja/issues/detail?id=1406
Darius Bacon

32

Dai un'occhiata all'ADsafe di Douglas Crockford :

ADsafe rende sicuro l'inserimento di codice ospite (come pubblicità o widget di terze parti) su qualsiasi pagina Web. ADsafe definisce un sottoinsieme di JavaScript sufficientemente potente da consentire al codice guest di eseguire interazioni utili, prevenendo al contempo danni o intrusioni dannose o accidentali. Il sottoinsieme ADsafe può essere verificato meccanicamente mediante strumenti come JSLint in modo che non sia necessaria alcuna ispezione umana per rivedere il codice degli ospiti per motivi di sicurezza. Il sottoinsieme ADsafe applica inoltre 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.htmle template.jsnel repository GitHub del progetto .


Sul loro sito non vedo alcun modo di usare ADsafe. Non c'è modo di scaricarlo, nessun link al codice, niente. Come puoi provare ADsafe?
BT,

2
Inoltre, impedisce qualsiasi accesso this, il che è del tutto inaccettabile. Non puoi scrivere un buon javascript senza usare this.
BT,

4
@BT Ho scritto interi progetti senza usare this. Non è difficile evitare il parametro mal nominato.
soundly_typed il

2
@BT Sarebbe sciocco dire che il completamento di progetti nel mondo reale è inaccettabile. Ma mi pento di aver iniziato questa discussione e devo ritirarmi; questo non è il posto giusto per discutere di queste cose (scusate). Sono su Twitter se vuoi discutere ulteriormente.
soundly_typed il

1
@BT (continuerò dal momento che è rilevante per la domanda) Ogni volta che esegui il codice nell'ambiente di qualcun altro ti imbatterai in regole e restrizioni. Non lo definirei inaccettabile. Un "dolore nel culo", forse. Ma non è inaccettabile. Dopotutto, per ogni uso di this, esiste un non- thismodo uguale ed equivalente per farlo (è solo un parametro, dopo tutto).
soundly_typed

24

Ho creato una libreria sandboxing chiamata jsandbox che utilizza i web worker per eseguire il sandbox del codice valutato. Ha anche un metodo di input per fornire esplicitamente dati in 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
      }
  });

+1: sembra davvero bello. Quanto è sicuro eseguire il codice dell'utente in questo modo?
Konstantin Tarkus

1
Molto sicuro. Dai un'occhiata alla libreria aggiornata su github .
Eli Gray,

1
questo progetto è ancora mantenuto? Vedo che non è stato aggiornato da oltre 2 anni ...
Yanick Rochon,

Mi piace questo, tranne per il fatto che se si desidera eseguire il sandbox ma consentire comunque l'accesso al codice per dire jQuery, ciò non riuscirebbe poiché gli operatori Web non consentono la manipolazione del DOM.
Rahly,

Ciao Eli, grazie per l'eccezionale lib, hai intenzione di mantenerlo? Ho una richiesta di modifica per l'aggiunta della funzionalità di debug, che guardando rapidamente il codice dovrebbe essere possibile. Per favore fatemi sapere cosa ne pensate?
user1514042,

8

Penso che valga la pena menzionare js.js qui. È un interprete JavaScript scritto in JavaScript.

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


7

Come menzionato in altre risposte, è sufficiente eseguire il jailing del codice in iframe sandbox (senza inviarlo sul 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 viene eseguito il codice non attendibile. E c'è anche una demo che esegue il codice inviato da un utente in un sandbox:

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


4

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

In generale, nessun grado di espressioni regolari, ecc. Può disinfettare in modo sicuro l'utente arbitrario fornito JavaScript poiché degenera il problema di arresto: - /


2
Puoi spiegare come degenera il problema dell'arresto?
hdgarrood

2
L'impossibilità teorica di risolvere il problema dell'arresto si applica solo all'analisi del codice statico. I sandbox possono fare cose come far rispettare i limiti di tempo per affrontare il problema dell'arresto.
Aviendha,

4

Una versione migliorata del codice sandbox dei lavoratori Web di @ RyanOHara, in un singolo file (non eval.jsè necessario alcun file aggiuntivo ).

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 essere prodotto 6(testato su Chrome e Firefox).


2

Un modo brutto ma forse questo funziona per te, ho preso tutti i globi e li ho ridefiniti nell'ambito sandbox, inoltre ho aggiunto la modalità rigorosa in modo che non possano ottenere l'oggetto 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


3
Triviale per windowtornare da quello. sandboxcode('console.log((0,eval)("this"))')
Ry-

Dovrò capire come
evitarlo

@alejandro Hai trovato un modo per impedirlo?
Wilt

1
La mia implementazione aggiunge semplicemente:function sbx(s,p) {e = eval; eval = function(t){console.log("GOT GOOD")}; sandboxcode(s,p); eval =e}
YoniXw il

2
@YoniXw: spero che tu non abbia finito per usarlo per niente. Nessun approccio come questo funzionerà mai. (_=>_).constructor('return this')()
Ry-

1

È più probabile che un interprete Javascript indipendente produca un sandbox robusto rispetto a una versione in gabbia dell'implementazione del browser integrata. Ryan ha già menzionato js.js , ma un progetto più aggiornato è JS-Interpreter . I documenti descrivono come esporre varie funzioni all'interprete, ma il suo scopo è altrimenti molto limitato.


1

A partire dal 2019, vm2 sembra la soluzione più popolare e aggiornata regolarmente a questo problema.


vm2 non supporta il runtime nel browser. Dovrebbe, tuttavia, funzionare se stai cercando il codice sandbox in un'app nodejs.
kevin.groat,

0

Con NISP sarai in grado di fare una valutazione sandbox. Sebbene l'espressione che scrivi non sia esattamente una JS, invece scriverai s-espressioni. Ideale per semplici DSL che non richiedono una programmazione estesa.


-3

1) Supponi di avere un codice da eseguire:

var sCode = "alert(document)";

Supponiamo ora di volerlo eseguire in un sandbox:

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

Queste due righe quando eseguite falliranno, perché la funzione "alert" non è disponibile dalla "sandbox"

2) E ora vuoi esporre un membro dell'oggetto window con le tue funzionalità:

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

In effetti è possibile aggiungere citazioni in escape e apportare altre lucidature, ma credo che l'idea sia chiara.


7
Non ci sono una miriade di altri modi per raggiungere l'oggetto globale? Ad esempio, all'interno di una funzione chiamata utilizzando func.apply (null) "this" sarà l'oggetto finestra.
mbarkhau,

5
Il primo esempio non fallisce, questo è un esempio molto non valido di sandboxing.
Andy E

1
var sCode = "this.alert ('FAIL')";
Leonard Pauli,

-4

Da dove proviene questo utente JavaScript?

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

Tuttavia, se lo script viene archiviato in un database, quindi recuperarlo ed eval (), è possibile ripulire lo script prima che venga eseguito.

Esempi di codice che rimuove tutta la finestra. e documento. Riferimenti:

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

Ciò tenta di impedire l'esecuzione (non testata) di quanto segue:

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" per JavaScript.


2
Se qualcuno sta cercando di fare qualcosa di dannoso, una semplice regex non può farlo - take (function () {this ["loca" + "tion"] = " example.com ";}) () In generale se non posso fidarmi dei vostri utenti (come nel caso di qualsiasi sito su cui persone arbitrarie possono aggiungere contenuti) è necessario bloccare tutti i js.
olliej,

Ho usato qualcosa di simile in passato. Non è perfetto, ma ti porta la maggior parte del tempo lì.
Sugendran,

Olliej, hai ragione sui limiti di una tale tecnica. Che ne dici di sovrascrivere variabili globali come <code> var window = null, document = null, this = {}; </code>?
Dimitry,

Dimitry Z, la sovrascrittura di queste variabili non è consentita [in alcuni browser]. Controlla anche la mia soluzione nell'elenco delle risposte: funziona.
Sergey Ilinsky,

-5

Ho lavorato su un sandbox js semplicistico per consentire agli utenti di creare applet per il mio sito. Anche se devo ancora affrontare alcune difficoltà nel consentire l'accesso al DOM (parentNode non mi consente di mantenere le cose sicure = /), il mio approccio era solo quello di ridefinire l'oggetto finestra 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 sto mostrando del tutto;)

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 far funzionare il codice. Inoltre, tutte le nuove variabili dichiarate all'interno del codice eval'd saranno infine legate all'ambito execute (), quindi non ci saranno nomi in conflitto o pasticciamento con il codice esistente.

Sebbene gli oggetti globali siano ancora accessibili, quelli che dovrebbero rimanere sconosciuti al codice sandbox devono essere definiti come proxy nell'oggetto Sandbox :: scope.

Spero che questo funzioni per te.


8
Questo non sandbox nulla. Il codice valutato può eliminare i membri e raggiungere l'ambito globale in quel modo, oppure prendere un riferimento all'ambito globale facendo (function () {return this;}) ()
Mike Samuel,

-6

Puoi racchiudere il codice dell'utente in una funzione che ridefinisce gli oggetti proibiti come parametri - questi sarebbero quindi undefinedquando chiamati:

(function (alert) {

alert ("uh oh!"); // User code

}) ();

Naturalmente, gli aggressori intelligenti possono aggirare questo problema ispezionando il DOM Javascript e trovando un oggetto non ignorato che contiene un riferimento alla finestra.


Un'altra idea è la scansione del codice dell'utente utilizzando uno strumento come jslint . Assicurati che sia impostato in modo da non avere variabili preimpostate (o: solo variabili che desideri), quindi se eventuali globi sono impostati o accessibili, non lasciare che lo script dell'utente venga utilizzato. Ancora una volta, potrebbe essere vulnerabile a camminare sugli oggetti DOM - gli oggetti che l'utente può costruire usando valori letterali potrebbero avere riferimenti impliciti all'oggetto finestra a cui è possibile accedere per uscire dalla sandbox.


2
Se l'utente immettesse window.alert anziché un semplice avviso, ignorerebbe tale limite.
Quentin,

@Dorward: sì, quindi "oggetti proibiti". wrunsby dovrebbe decidere a quali oggetti l'utente non può accedere e inserirli nell'elenco dei parametri.
John Millikin,

C'è solo un oggetto: finestra. Se non blocchi l'accesso ad esso, allora tutto è disponibile attraverso di esso. Se lo blocchi, lo script non può accedere a nessuna delle sue proprietà (poiché dire alert invece di window.alert implica semplicemente la finestra).
Quentin,

@Doward: non è il caso in cui si bloccherebbe window.alert ma l'avviso funzionerebbe comunque, provalo. Questo perché window è anche l'oggetto globale. Uno dovrebbe bloccare la finestra e qualsiasi proprietà o metodo della finestra a cui non si desidera che acceda al codice utente.
AnthonyWJones,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.