Ist es möglich, Sandbox-JavaScript im Browser auszuführen?

142

Ich frage mich, ob es möglich ist, JavaScript im Browser zu sandboxen, um den Zugriff auf Funktionen zu verhindern, die normalerweise für JavaScript-Code verfügbar sind, der auf einer HTML-Seite ausgeführt wird.

Angenommen, ich möchte Endbenutzern eine JavaScript-API bereitstellen, mit der sie Ereignishandler definieren können, die ausgeführt werden sollen, wenn "interessante Ereignisse" auftreten. Ich möchte jedoch nicht, dass diese Benutzer auf die Eigenschaften und Funktionen des windowObjekts zugreifen . Kann ich das machen?

Nehmen wir im einfachsten Fall an, ich möchte verhindern, dass Benutzer anrufen alert. Einige Ansätze, die mir einfallen, sind:

  • Neu definiert window.alertglobal. Ich denke nicht, dass dies ein gültiger Ansatz wäre, da möglicherweise anderer Code, der auf der Seite ausgeführt wird (dh Dinge, die nicht von Benutzern in ihren Ereignishandlern erstellt wurden), verwendet werden möchte alert.
  • Senden Sie den Ereignishandlercode zur Verarbeitung an den Server. Ich bin nicht sicher, ob das Senden des Codes an den Server zur Verarbeitung der richtige Ansatz ist, da die Ereignishandler im Kontext der Seite ausgeführt werden müssen.

Vielleicht würde eine Lösung funktionieren, bei der der Server die benutzerdefinierte Funktion verarbeitet und dann einen Rückruf generiert, der auf dem Client ausgeführt werden soll? Auch wenn dieser Ansatz funktioniert, gibt es bessere Möglichkeiten, dieses Problem zu lösen?

Walter Rumsby
quelle

Antworten:

54

Google Caja ist ein Source-to-Source-Übersetzer, mit dem Sie "nicht vertrauenswürdiges HTML und JavaScript von Drittanbietern in Ihre Seite einbinden und trotzdem sicher sein können".

Darius Bacon
quelle
5
Ein schneller Test zeigt, dass Caja den Browser nicht vor CPU-Angriffen wie while (1) {}--- schützen kann. Er hängt einfach. Ebenso a=[]; while (1) { a=[a,a]; }.
David gegeben
5
Ja, Denial-of-Service ist nicht möglich: code.google.com/p/google-caja/issues/detail?id=1406
Darius Bacon
32

Schauen Sie sich Douglas Crockfords ADsafe an :

ADsafe macht es sicher, Gastcode (z. B. Skriptwerbung von Drittanbietern oder Widgets) auf einer beliebigen Webseite zu platzieren. ADsafe definiert eine Teilmenge von JavaScript, die leistungsfähig genug ist, um Gastcode wertvolle Interaktionen ausführen zu lassen und gleichzeitig böswilligen oder versehentlichen Schaden oder Eindringen zu verhindern. Die ADsafe-Teilmenge kann mit Tools wie JSLint mechanisch überprüft werden, sodass keine menschliche Inspektion erforderlich ist, um den Gastcode auf Sicherheit zu überprüfen. Die ADsafe-Teilmenge erzwingt auch gute Codierungspraktiken und erhöht die Wahrscheinlichkeit, dass Gastcode ordnungsgemäß ausgeführt wird.

Ein Beispiel für die Verwendung von ADsafe finden Sie in den Dateien template.htmlund template.jsim GitHub-Repository des Projekts .

Simon Lieschke
quelle
Auf ihrer Website sehe ich keine Möglichkeit, ADsafe zu verwenden. Es gibt keine Möglichkeit, es herunterzuladen, keinen Link zum Code, nichts. Wie können Sie ADsafe ausprobieren?
BT
2
Außerdem wird jeglicher Zugriff auf verhindert this, was völlig inakzeptabel ist. Sie können kein gutes Javascript schreiben, ohne es zu verwenden this.
BT
4
@ BT Ich habe ganze Projekte ohne Verwendung geschrieben this. Es ist nicht schwer, den schlecht benannten Parameter zu vermeiden.
soundly_typed
2
@BT Es wäre albern zu sagen, dass das Abschließen realer Projekte nicht akzeptabel ist. Aber ich bedauere es, diese Diskussion begonnen zu haben, und muss mich zurückziehen. Dies ist nicht der Ort, um solche Dinge zu besprechen (sorry). Ich bin auf Twitter, wenn Sie weiter diskutieren möchten.
soundly_typed
1
@BT (Ich werde fortfahren, da dies für die Frage relevant ist.) Jedes Mal, wenn Sie Code in einer anderen Umgebung ausführen, werden Sie auf Regeln und Einschränkungen stoßen. Ich würde das nicht als inakzeptabel bezeichnen. Vielleicht ein "Schmerz im Arsch". Aber nicht inakzeptabel. Schließlich gibt es für jede Verwendung von thiseinen gleichen, äquivalenten thisWeg, dies nicht zu tun (es ist schließlich nur ein Parameter).
soundly_typed
24

Ich habe eine Sandbox- Bibliothek namens jsandbox erstellt , die Web-Worker verwendet, um ausgewerteten Code zu sandboxen . Es gibt auch eine Eingabemethode zum expliziten Angeben von Sandbox-Codedaten, die sonst nicht abgerufen werden könnten.

Das Folgende ist ein Beispiel für die 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
      }
  });
Eli Gray
quelle
+1: Das sieht wirklich cool aus. Wie sicher ist es, Benutzercode auf diese Weise auszuführen?
Konstantin Tarkus
1
Sehr sicher. Schauen Sie sich die aktualisierte Bibliothek auf Github an .
Eli Gray
1
Wird dieses Projekt noch gepflegt? Ich sehe, dass es seit über 2 Jahren nicht aktualisiert wurde ...
Yanick Rochon
Ich mag dies, außer dass, wenn Sie eine Sandbox verwenden möchten, aber dennoch den Codezugriff auf jQuery zulassen möchten, dies fehlschlagen würde, da Web-Worker keine DOM-Manipulation zulassen.
Rahly
Hallo Eli, danke für eine großartige Bibliothek. Hast du vor, sie zu pflegen? Ich habe eine Änderungsanforderung zum Hinzufügen von Debugging-Funktionen, die durch schnelles Betrachten des Codes möglich sein sollten. Bitte sag mir was du denkst?
Benutzer1514042
8

Ich denke, dass js.js hier erwähnenswert ist. Es ist ein in JavaScript geschriebener JavaScript-Interpreter.

Es ist ungefähr 200-mal langsamer als natives JS, aber seine Natur macht es zu einer perfekten Sandbox-Umgebung. Ein weiterer Nachteil ist seine Größe - fast 600 KB, was in einigen Fällen für Desktops akzeptabel sein kann, für mobile Geräte jedoch nicht.

gronostaj
quelle
7

Wie in anderen Antworten erwähnt, reicht es aus, den Code in einem Sandbox-Iframe zu speichern (ohne ihn an den Server zu senden) und mit Nachrichten zu kommunizieren. Ich würde vorschlagen, einen Blick auf eine kleine Bibliothek zu werfen, die ich hauptsächlich erstellt habe, weil eine API für den nicht vertrauenswürdigen Code bereitgestellt werden muss, genau wie in der Frage beschrieben: Es besteht die Möglichkeit, den jeweiligen Funktionssatz direkt in die Sandbox zu exportieren, in der Der nicht vertrauenswürdige Code wird ausgeführt. Außerdem gibt es eine Demo, die den von einem Benutzer in einer Sandbox übermittelten Code ausführt:

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

asvd
quelle
4

Alle Browser-Anbieter und die HTML5-Spezifikation arbeiten auf eine tatsächliche Sandbox-Eigenschaft hin, um Sandbox-Iframes zuzulassen. Sie ist jedoch weiterhin auf die Iframe-Granularität beschränkt.

Im Allgemeinen kann kein Grad an regulären Ausdrücken usw. willkürlich von Benutzern bereitgestelltes JavaScript sicher bereinigen, da es zum Stoppproblem degeneriert: - /

olliej
quelle
2
Können Sie erklären, wie es zum Stillstandsproblem degeneriert?
HDGARROOD
2
Die theoretische Unmöglichkeit, das Halteproblem zu lösen, gilt nur für die statische Code-Analyse. Sandboxen können beispielsweise Zeitlimits durchsetzen, um das Problem des Anhaltens zu lösen.
Aviendha
4

Eine verbesserte Version des Sandbox-Codes für Webworker von @ RyanOHara in einer einzigen Datei (keine zusätzliche eval.jsDatei erforderlich).

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

Probier es aus:

https://jsfiddle.net/kp0cq6yw/

var promise = safeEval("1+2+3");

promise.then(function (result) {
      alert(result);
      });

Es sollte ausgegeben werden 6(getestet in Chrome und Firefox).

MarcG
quelle
2

Ein hässlicher Weg, aber vielleicht funktioniert das für Sie. Ich habe alle Globals genommen und sie im Sandbox-Bereich neu definiert. Außerdem habe ich den strengen Modus hinzugefügt, damit sie das globale Objekt nicht mit einer anonymen Funktion abrufen können.

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

Alejandro
quelle
3
Es ist trivial, windowdarauf zurückzukommen. sandboxcode('console.log((0,eval)("this"))')
Ry-
Ich muss herausfinden, wie ich das verhindern kann
Alejandro
@alejandro Hast du einen Weg gefunden, das zu verhindern?
Wilt
1
Meine Implementierung fügt nur hinzu:function sbx(s,p) {e = eval; eval = function(t){console.log("GOT GOOD")}; sandboxcode(s,p); eval =e}
YoniXw
2
@YoniXw: Ich hoffe, du hast es nicht für irgendetwas benutzt. Kein solcher Ansatz wird jemals funktionieren. (_=>_).constructor('return this')()
Ry-
1

Ab 2019 scheint vm2 die beliebteste und am regelmäßigsten aktualisierte Lösung für dieses Problem zu sein.

Bret Cameron
quelle
vm2 unterstützt keine Laufzeit im Browser. Es sollte jedoch funktionieren, wenn Sie Sandbox-Code in einer NodeJS-App suchen.
kevin.groat
0

Mit NISP können Sie eine Sandbox-Bewertung durchführen. Obwohl der Ausdruck, den Sie schreiben, nicht genau ein JS ist, schreiben Sie stattdessen S-Ausdrücke. Ideal für einfache DSLs, die keine umfangreiche Programmierung erfordern.

Kannan Ramamoorthy
quelle
-3

1) Angenommen, Sie müssen einen Code ausführen:

var sCode = "alert(document)";

Angenommen, Sie möchten es in einer Sandbox ausführen:

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

Diese beiden Zeilen schlagen bei der Ausführung fehl, da die Funktion "alert" in der "Sandbox" nicht verfügbar ist.

2) Und jetzt möchten Sie ein Mitglied des Fensterobjekts mit Ihrer Funktionalität verfügbar machen:

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

In der Tat können Sie Anführungszeichen hinzufügen und andere polieren, aber ich denke, die Idee ist klar.

Sergey Ilinsky
quelle
7
Gibt es nicht unzählige andere Möglichkeiten, um an das globale Objekt zu gelangen? Zum Beispiel innerhalb einer Funktion, die mit func.apply (null) aufgerufen wird, ist "this" das Fensterobjekt.
mbarkhau
5
Das erste Beispiel schlägt nicht fehl, dies ist ein sehr ungültiges Beispiel für Sandboxing.
Andy E
1
var sCode = "this.alert ('FAIL')";
Leonard Pauli
-4

Woher kommt dieser Benutzer JavaScript?

Sie können nicht viel tun, wenn ein Benutzer Code in Ihre Seite einbettet und ihn dann über seinen Browser aufruft (siehe Greasemonkey, http://www.greasespot.net/ ). Es ist nur etwas, was Browser tun.

Wenn Sie das Skript jedoch in einer Datenbank speichern, dann abrufen und auswerten (), können Sie das Skript bereinigen, bevor es ausgeführt wird.

Beispiele für Code, der alle Fenster entfernt. und dokumentieren. Verweise:

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

Dies versucht zu verhindern, dass Folgendes ausgeführt wird (nicht getestet):

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

Es gibt viele Einschränkungen, die Sie auf das unsichere Benutzerskript anwenden müssten. Leider ist für JavaScript kein 'Sandbox-Container' verfügbar.

Dimitry
quelle
2
Wenn jemand versucht, etwas Bösartiges zu tun, kann ein einfacher Regex dies einfach nicht tun - take (function () {this ["loca" + "tion"] = " example.com ";}) () Im Allgemeinen, wenn Sie Ich kann Ihren Benutzern nicht vertrauen (was bei jeder Site der Fall ist, auf der beliebige Personen Inhalte hinzufügen können). Das Blockieren aller JS ist erforderlich.
olliej
Ich habe in der Vergangenheit etwas Ähnliches verwendet. Es ist nicht perfekt, aber es bringt Sie den größten Teil des Weges dorthin.
Sugendran
olliej, Sie haben Recht mit den Einschränkungen einer solchen Technik. Wie wäre es mit dem Überschreiben globaler Variablen wie <code> var window = null, document = null, this = {}; </ code>?
Dimitry
Dimitry Z, das Überschreiben dieser Variablen ist nicht zulässig [in einigen Browsern]. Überprüfen Sie auch meine Lösung in der Liste der Antworten - es funktioniert.
Sergey Ilinsky
-5

Ich habe an einer simplen JS-Sandbox gearbeitet, mit der Benutzer Applets für meine Site erstellen können. Obwohl ich immer noch vor einigen Herausforderungen stehe, wenn ich den DOM-Zugriff zulasse (parentNode lässt mich die Dinge einfach nicht sicher halten = /), bestand mein Ansatz darin, das Fensterobjekt mit einigen seiner nützlichen / harmlosen Mitglieder neu zu definieren und dann den Benutzer zu bewerten () Code mit diesem neu definierten Fenster als Standardbereich.

Mein "Kern" -Code sieht so aus ... (Ich zeige ihn nicht ganz;)

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

Also kann ich eine Sandbox instanziieren und ihre execute () verwenden, um Code zum Laufen zu bringen. Außerdem werden alle neu deklarierten Variablen im ausgewerteten Code letztendlich an den Bereich execute () gebunden, sodass keine Namen in Konflikt geraten oder mit vorhandenem Code in Konflikt geraten.

Obwohl globale Objekte weiterhin zugänglich sind, müssen diejenigen, die dem Sandbox-Code unbekannt bleiben sollten, als Proxys im Sandbox :: scope-Objekt definiert werden.

Hoffe das funktioniert bei dir.


quelle
8
Dies Sandbox nichts. Der ausgewertete Code kann Mitglieder löschen und auf diese Weise zum globalen Bereich gelangen oder einen Verweis auf den globalen Bereich abrufen, indem Sie (function () {return this;}) () ausführen
Mike Samuel
-6

Sie können den Code des Benutzers in eine Funktion einschließen, die verbotene Objekte als Parameter neu definiert. Diese werden dann undefinedbeim Aufruf angezeigt:

(function (alert) {

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

}) ();

Kluge Angreifer können dies natürlich umgehen, indem sie das Javascript-DOM untersuchen und ein nicht überschriebenes Objekt finden, das einen Verweis auf das Fenster enthält.


Eine andere Idee ist das Scannen des Benutzercodes mit einem Tool wie jslint . Stellen Sie sicher, dass keine voreingestellten Variablen vorhanden sind (oder: nur die gewünschten Variablen), und lassen Sie das Skript des Benutzers nicht verwendet werden, wenn globale Werte festgelegt sind oder auf diese zugegriffen wird. Auch hier kann es anfällig sein, das DOM zu durchlaufen - Objekte, die der Benutzer mithilfe von Literalen erstellen kann, enthalten möglicherweise implizite Verweise auf das Fensterobjekt, auf das zugegriffen werden kann, um die Sandbox zu verlassen.

John Millikin
quelle
2
Wenn der Benutzer window.alert anstelle einer einfachen Warnung eingibt, wird diese Grenze umgangen.
Quentin
@Dorward: ja, daher "verbotene Objekte". wrunsby sollte entscheiden, auf welche Objekte der Benutzer nicht zugreifen darf, und sie in die Parameterliste aufnehmen.
John Millikin
Es gibt nur ein Objekt - Fenster. Wenn Sie den Zugriff darauf nicht blockieren, ist alles über ihn verfügbar. Wenn Sie es blockieren, kann das Skript auf nichts von seinen Eigenschaften zugreifen (da Alert anstelle von window.alert nur das Fenster impliziert.).
Quentin
@Doward: Das ist nicht der Fall, Sie würden window.alert blockieren, aber Alert würde immer noch funktionieren, versuchen Sie es. Dies liegt daran, dass Fenster auch das globale Objekt ist. Man müsste das Fenster und jede Eigenschaft oder Methode des Fensters blockieren, auf die der Benutzercode nicht zugreifen soll.
AnthonyWJones