Ein kurzer Vortrag über JavaScript Sandbox

Ein kurzer Vortrag über JavaScript Sandbox

Vorwort:

Apropos Sandboxen: Wir denken vielleicht reflexartig an das obige Bild und sind sofort interessiert, aber leider geht es in diesem Artikel nicht um „Minecraft“ (eine alte Cover-Party). Der folgende Artikel wird schrittweise die Sandbox von „Browser World“ vorstellen.

1. Was ist eine Sandbox?

In der Computersicherheit ist eine Sandbox ein Sicherheitsmechanismus, der zum Isolieren laufender Programme verwendet wird. Sie wird normalerweise zum Ausführen ungetesteter oder nicht vertrauenswürdiger Programme oder Codes verwendet. Sie erstellt eine unabhängige Ausführungsumgebung für das auszuführende Programm, und die Ausführung des internen Programms hat keinen Einfluss auf den Betrieb des externen Programms.

Die folgenden Szenarios beinhalten beispielsweise das abstrakte Konzept einer Sandbox:

  • Das von uns entwickelte Seitenprogramm läuft im Browser. Das Programm kann nur den Teil der Benutzeroberfläche ändern, den der Browser uns ändern lässt. Wir können den Status außerhalb des Browsers durch dieses Skript nicht beeinflussen. In diesem Szenario ist der Browser selbst eine Sandbox.
  • Auf jedem Tab im Browser wird eine unabhängige Webseite ausgeführt, und die einzelnen Tabs beeinflussen sich gegenseitig nicht. Dieser Tab ist eine Sandbox.
  • ......

2. Was sind die Anwendungsszenarien von Sandbox?

Oben werden einige relativ makroökonomische Sandbox-Szenarien vorgestellt. Tatsächlich gibt es in der täglichen Entwicklung viele Szenarien, die die Anwendung eines solchen Mechanismus erfordern:

  • Wenn Sie die von JSONP Anforderung zurückgegebene Zeichenfolge ausführen oder eine unbekannte JS Bibliothek eines Drittanbieters einführen, müssen Sie möglicherweise eine Sandbox erstellen, um diese Codes auszuführen.
  • Die Berechnung von Vue-Vorlagenausdrücken wird in einer Sandbox ausgeführt. Die Ausdrücke in der Vorlagenzeichenfolge können nur einige globale Objekte abrufen. Dies wird in der offiziellen Dokumentation erwähnt. Einzelheiten finden Sie im Quellcode

  • Online-Code-Editoren wie CodeSanbox platzieren das Programm beim Ausführen des Skripts in einer Sandbox, um zu verhindern, dass das Programm auf die Hauptseite zugreift bzw. diese beeinflusst.
  • Viele Anwendungen bieten einen Plugin -Mechanismus und Entwickler können ihre eigenen Plug-Ins schreiben, um bestimmte benutzerdefinierte Funktionen zu implementieren. Studenten, die Plug-Ins entwickelt haben, sollten wissen, dass es bei der Entwicklung von Plug-Ins viele Einschränkungen gibt. Diese Anwendungen müssen beim Ausführen von Plug-Ins die vom Hostprogramm festgelegten Betriebsregeln befolgen. Die Betriebsumgebung und die Regeln des Plug-Ins sind eine Sandbox. Die folgende Abbildung zeigt beispielsweise, wie Figma -Plugin funktioniert:

Kurz gesagt, solange wir auf nicht vertrauenswürdigen Code von Drittanbietern stoßen, können wir eine Sandbox verwenden, um den Code zu isolieren und so den stabilen Betrieb externer Programme sicherzustellen. Wenn nicht vertrauenswürdiger Code ohne jegliche Verarbeitung ausgeführt wird, besteht die offensichtlichste Nebenwirkung/der offensichtlichste Schaden im Front-End in der Verschmutzung und Manipulation des globalen window , was sich auf die Funktion der Hauptseite auswirkt und sogar zu XSS-Angriffen führen kann.

// Unteranwendungscode window.location.href = 'www.diaoyu.com'

Objekt.prototype.toString = () => {

    console.log('Du bist ein Narr :)')

  }

document.querySelectorAll('div').fürEach(node ​​​​=> node.classList.add('hhh'))

sendRequest(document.cookie)

...

3. So implementieren Sie eine JS-Sandbox

Um eine Sandbox zu implementieren, muss eigentlich ein Mechanismus zur Programmausführung entwickelt werden. Unter der Wirkung dieses Mechanismus hat die Ausführung des Programms innerhalb der Sandbox keinen Einfluss auf die Ausführung des externen Programms.

3.1 Die einfachste Sandbox

Um diesen Effekt zu erzielen, besteht die direkteste Idee darin, dass alle im Programm aufgerufenen Variablen aus einer zuverlässigen oder autonomen Kontextumgebung stammen, anstatt Werte aus der globalen Ausführungsumgebung zu übernehmen. Um dann zu erreichen, dass auf alle Variablen aus einer zuverlässigen Kontextumgebung zugegriffen wird,

Wir müssen einen Rahmen für die Ausführung des Programms konstruieren:

//Ausführungskontextobjekt const ctx = 
    Funktion: Variable => {
        console.log(Variable)
    },
    foo: "foo"
}

// Die einfachste Sandbox-Funktion poorestSandbox(code, ctx) {
    eval(code) // konstruiert einen Funktionsumfang zur Ausführung des Programms}

// Auszuführendes Programm const code = `
    ctx.foo = "Leiste"
    ctx.func(ctx.foo)
`

poorestSandbox(Code, CTX) // Balken

Eine solche Sandbox erfordert, dass das Quellprogramm beim Abrufen einer beliebigen Variable das Präfix des Ausführungskontextobjekts hinzufügt, was offensichtlich sehr unvernünftig ist, da wir keine Möglichkeit haben, das Verhalten Dritter zu kontrollieren. Gibt es eine Möglichkeit, dieses Präfix zu entfernen?

3.2 Eine sehr einfache Sandbox (Mit)

Mithilfe der with -Anweisung können wir dieses Präfix entfernen. with fügt am Anfang der Bereichskette einen neuen Bereich hinzu. Das Variablenobjekt dieses Bereichs wird dem von with übergebenen Objekt hinzugefügt. Daher wird der interne Code bei der Suche nach Variablen im Vergleich zur externen Umgebung die Suche nach diesem Objekt priorisieren.

//Ausführungskontextobjekt const ctx = {
    Funktion: Variable => {
        console.log(Variable)
    },
    foo: "foo"
}

// Sehr schlechte Sandbox-Funktion veryPoorSandbox(code, ctx) {
    mit(ctx) { // Hinzufügen mit
        eval(Code)
    }
}

// Auszuführendes Programm const code = `
    foo = "Leiste"
    Funktion(foo)
`

veryPoorSandbox(Code, ctx) // Balken

Dadurch wird erreicht, dass nach Variablen im ausgeführten Programm im Kontext gesucht wird, der von der Sandbox bereitgestellt wird, bevor in der externen Ausführungsumgebung nach Variablen gesucht wird.

Das Problem besteht darin, dass der Code, wenn eine Variable im bereitgestellten Kontextobjekt nicht gefunden wird, trotzdem die Bereichskette Schicht für Schicht durchsucht. Eine solche Sandbox kann die Ausführung des internen Codes immer noch nicht steuern. Wir möchten, dass der Code in der Sandbox nur nach Variablen im manuell bereitgestellten Kontextobjekt sucht und einen Fehler meldet oder undefined zurückgibt, wenn die Variable im Kontextobjekt nicht vorhanden ist.

3.3 Nicht ganz so einfache Sandbox (mit + Proxy)

Um die oben genannten Probleme zu lösen, verwenden wir eine neue Funktion von ES2015Proxy . Proxy kann ein Objekt proxyen und so die grundlegenden Operationen des Objekts abfangen und definieren.

Die Get- und Set-Methoden in Proxy können nur Eigenschaften abfangen, die bereits im Proxy-Objekt vorhanden sind. Diese beiden Hooks erkennen keine Eigenschaften, die im Proxy-Objekt nicht vorhanden sind. Daher verwenden wir hier Proxy.has() um den Zugriff auf jede Variable im Codeblock with abzufangen und eine Whitelist festzulegen. Auf die Variablen in der Whitelist kann normal über die Scope-Kette zugegriffen werden. Bei den Variablen, die nicht in der Whitelist sind, wird weiterhin ermittelt, ob sie im von der Sandbox verwalteten Kontextobjekt vorhanden sind. Wenn sie vorhanden sind, wird normal auf sie zugegriffen. Wenn sie nicht vorhanden sind, wird direkt ein Fehler gemeldet.

Da has alle Variablenzugriffe im with -Codeblock abfängt und wir das Programm nur im ausgeführten Codeblock überwachen möchten, müssen wir zusätzlich die Form der manuellen Codeausführung umwandeln:

// Erstelle ein with, um den auszuführenden Code zu umschließen und gib eine Funktionsinstanz des with-Codeblocks zurück function withedYourCode(code) {
  Code = 'mit(globalObj) {' + Code + '}'
  gibt neue Funktion zurück('globalObj', Code)
}


// Whitelist der globalen Bereiche, auf die zugegriffen werden kann const access_white_list = ['Math', 'Date']


// Auszuführendes Programm const code = `
    Math.random()
    Standort.href = "xxx"
    Funktion(foo)
`

//Ausführungskontextobjekt const ctx = {
    Funktion: Variable => {
        console.log(Variable)
    },
    foo: "foo"
}

// Proxy-Objekt des Ausführungskontextobjekts const ctxProxy = new Proxy(ctx, {
    has: (target, prop) => { // has kann den Zugriff auf jede Eigenschaft im with-Codeblock abfangen if (access_white_list.includes(prop)) { // In der zugänglichen Whitelist kann weiter nach oben gesucht werden return target.hasOwnProperty(prop)
      }

      wenn (!target.hasOwnProperty(prop)) {
          throw new Error(`Ungültiger Ausdruck – ${prop}! Das kannst du nicht machen!`)
      }

      returniere wahr
    }
})

// Nicht so arme Sandbox-Funktion littlePoorSandbox(code, ctx) {

    withedYourCode(code).call(ctx, ctx) // verweisen Sie dies auf das manuell erstellte globale Proxy-Objekt}


littlePoorSandbox(Code, ctxProxy)

// Nicht abgefangener Fehler: Ungültiger Ausdruck - Standort! Das kannst du nicht machen!

An diesem Punkt können viele relativ einfache Szenarien abgedeckt werden ( eg: Vue ), aber was ist, wenn Sie einen web Editor wie CodeSanbox implementieren möchten? In einem solchen Editor können wir globale Variablen wie document und location nach Belieben verwenden, ohne die Hauptseite zu beeinträchtigen.

Dies führt zu einer weiteren Frage: Wie kann man die Subroutine alle globalen Objekte verwenden lassen, ohne den externen globalen Status zu beeinflussen?

3.4 Natürliche, hochwertige Sandbox (iframe)

Als ich die obige Frage hörte, nannte ich mich sofort einen Experten. iframe iframe kann eine unabhängige Browser-native Betriebsumgebung erstellen, die vom Browser von der Hauptumgebung isoliert ist. Die globalen Objekte, auf die das im iframe ausgeführte Skriptprogramm zugreift, werden alle vom aktuellen iframe Ausführungskontext bereitgestellt und wirken sich nicht auf die Hauptfunktionen der übergeordneten Seite aus. Daher ist die Verwendung iframe zur Implementierung einer Sandbox derzeit die bequemste, einfachste und sicherste Methode.

Stellen Sie sich ein Szenario wie dieses vor: Auf einer Seite gibt es mehrere Sandbox-Fenster, von denen eines einige globale Zustände mit der Hauptseite teilen muss (z. B.: wenn Sie auf die Zurück-Schaltfläche des Browsers klicken, kehrt auch die untergeordnete Anwendung zur vorherigen Ebene zurück) und eine andere Sandbox muss einige andere globale Zustände mit der Hauptseite teilen (z. B.: Cookie-Anmeldezustand teilen).

Obwohl der Browser postMessage und andere Methoden für die Kommunikation zwischen der Hauptseite und iframe bereitstellt, ist es schwierig und nicht wartungsfreundlich, dieses Szenario nur mit dem Iframe umzusetzen.

3.5 sollte die Sandbox nutzen können (Mit + Proxy + iframe)

Um das obige Szenario zu erreichen, können wir die oben genannten Methoden zusammenfügen:

  • Unter Ausnutzung der natürlichen Isolation von iframe von globalen Objekten wird iframe.contentWindow als globales Objekt herausgenommen, das in der aktuellen Sandbox ausgeführt wird.
  • Verwenden Sie das globale Sandbox-Objekt als Parameter, um den Zugriff des intern ausgeführten Programms einzuschränken, und verwenden Sie Proxy , um den Zugriff innerhalb des Programms zu überwachen.
  • Pflegen Sie eine Liste gemeinsam genutzter Status, listen Sie die globalen Status auf, die mit der Außenwelt geteilt werden müssen, und implementieren Sie die Zugriffskontrolle innerhalb Proxy .
//Sandbox globale Proxy-Objektklasse class SandboxGlobalProxy {

    Konstruktor(gemeinsamerZustand) {
        // Ein Iframe-Objekt erstellen und das native globale Browserobjekt als globales Objekt der Sandbox herausnehmen const iframe = document.createElement('iframe', {url: 'about:blank'})
        Dokument.Body.AnhängenUntergeordnetesElement(iframe)
        const sandboxGlobal = iframe.contentWindow // Globales Objekt der Sandbox-Laufzeit return new Proxy(sandboxGlobal, {
            has: (target, prop) => { // has kann den Zugriff auf jede Eigenschaft im with-Codeblock abfangen if (sharedState.includes(prop)) { // Wenn die Eigenschaft im gemeinsam genutzten globalen Status vorhanden ist, lass es die äußere Schicht entlang der Prototypenkette durchsuchen return false
                }

                wenn (!target.hasOwnProperty(prop)) {
                    throw new Error(`Ungültiger Ausdruck – ${prop}! Das kannst du nicht machen!`)
                }
                returniere wahr
            }
        })

    }

}


Funktion vielleichtVerfügbareSandbox(Code, ctx) {

    mit IhremCode(Code).call(ctx, ctx)

}

const code_1 = `

    console.log(Verlauf == Fenster.Verlauf) // false

    fenster.abc = "Sandbox"

    Objekt.prototype.toString = () => {

        console.log('Gefangen!')

    }

    konsole.log(window.abc) // Sandbox

`

const sharedGlobal_1 = ['history'] // Globales Objekt, das Sie mit der externen Ausführungsumgebung teilen möchten const globalProxy_1 = new SandboxGlobalProxy(sharedGlobal_1)

vielleichtVerfügbareSandbox(code_1, globalProxy_1)


window.abc // undefiniert

Object.prototype.toString() // [Objekt Object] druckt nicht Traped

Anhand der Ergebnisse des Beispielcodes können wir erkennen, dass wir durch die Nutzung der Vorteile der natürlichen Umgebungsisolierung von iframe und der leistungsstarken Steuerung von with + Proxy die Isolierung globaler Objekte in der Sandbox und globaler Objekte in der äußeren Schicht erreicht und die gemeinsame Nutzung einiger globaler Eigenschaften realisiert haben.

3.6 Sandbox-Flucht

Sandbox ist eine Sicherheitsstrategie für Autoren , kann für Benutzer jedoch eine Einschränkung darstellen. Kreative Entwickler versuchen auf verschiedene Weise diese Einschränkung zu beseitigen, was auch als Sandbox Escape bezeichnet wird. Daher besteht die größte Herausforderung für ein Sandbox-Programm darin, diese unerwarteten Programme zu erkennen und ihre Ausführung zu verhindern.

Die oben implementierte Sandbox scheint unsere Anforderungen zu erfüllen. Ist sie fertig? Tatsächlich wirken sich die folgenden Vorgänge auf die Umgebung außerhalb der Sandbox aus und ermöglichen ein Verlassen der Sandbox:

Beim Zugriff auf eine interne Eigenschaft eines Objekts im Sandbox-Ausführungskontext kann Proxy den Zugriffsvorgang dieser Eigenschaft nicht erfassen . Beispielsweise können wir das äußere globale Objekt direkt über window.parent im Ausführungskontext der Sandbox abrufen.

// Beim Zugriff auf die Eigenschaften eines Objekts in einem Sandbox-Objekt wird ein Teil des obigen Codes ausgelassen const ctx = {

    Fenster: {

        übergeordnetes Element: {...},

        ...

    }

}

Konstantencode = `

    fenster.übergeordnet.abc = "xxx"

`

fenster.abc // xxx

  • Durch Zugriff auf die Prototypenkette zum Erreichen eines Escape-Vorgangs kann JS direkt ein Literal deklarieren und dann die Prototypenkette des Literals durchsuchen, um auf das äußere globale Objekt zuzugreifen. Auch dieses Verhalten ist nicht wahrnehmbar.
Konstantencode = `

    ({}).constructor.prototype.toString = () => {

        console.log('Escape!')

    }

`

({}).toString() // Escape! Erwartet [Objekt Objekt]

3.7 „Flawless“ Sandbox (Interpreter anpassen)

Bei der Implementierung einer Sandbox mit den oben genannten Methoden gibt es mehr oder weniger einige Mängel. Gibt es eine Sandbox, die fast fertig ist?

Tatsächlich tun dies viele Open-Source-Bibliotheken bereits, d. h. sie analysieren die Struktur des Quellprogramms, um die Ausführungslogik jeder Anweisung manuell zu steuern. Auf diese Weise bleiben sowohl der Kontext der Angabe der Programmlaufzeit als auch die Erfassung von Vorgängen, die versuchen, der Sandbox-Steuerung zu entgehen, unter Kontrolle. Die Implementierung einer solchen Sandbox ist im Wesentlichen die Implementierung eines benutzerdefinierten Interpreters.

Funktion AlmostPerfectSandbox (Code, CTX, illegalOperations) {

    return myInterpreter(code, ctx, illegalOperations) // benutzerdefinierter Interpreter }

4. Zusammenfassung

Dieser Artikel stellt hauptsächlich die grundlegenden Konzepte und Anwendungsszenarien von Sandboxen vor und gibt Ihnen Anregungen für die Implementierung einer JavaScript-Sandbox. Die Implementierungsmethode der Sandbox ist nicht statisch und ihre Ziele sollten in Kombination mit bestimmten Szenarien analysiert werden. Darüber hinaus ist das Verhindern von Sandbox-Ausbrüchen ebenfalls eine langwierige und mühsame Aufgabe, da es schwierig ist, alle case in den frühen Konstruktionsphasen abzudecken.

Kein Sandkasten wird über Nacht zusammengebaut, wie bei Minecraft.

5. Referenz

Quellen:

Quellcode: https://github.com/vuejs/vue/blob/v2.6.10/src/core/instance/proxy.js
CodeSanbox: https://codesandbox.io/
mit: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with
Proxy: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
CodeSanbox: https://codesandbox.io/
Schreiben eines JavaScript-Frameworks – Sandboxed-Code-Evaluation: https://blog.risingstack.com/writing-a-javascript-framework-sandboxed-code-evaluation/
Sprechen Sie über die Sandbox in JS: https://juejin.cn/post/6844903954074058760#heading-1

Das könnte Sie auch interessieren:
  • Quickjs kapselt JavaScript-Sandbox-Details
  • JavaScript-Sandbox-Erkundung
  • Eine kurze Diskussion über verschiedene Möglichkeiten zur Implementierung einer Front-End-JS-Sandbox
  • Eine kurze Diskussion über die Node.js-Sandbox-Umgebung
  • Einrichten einer sicheren Sandbox-Umgebung für Node.js-Anwendungen
  • Beispiel für den Sandbox-Modus beim Schließen der JS-Implementierung
  • Beispielanalyse des JS-Sandbox-Modus
  • Sicherheits-Sandbox-Modus des JavaScript-Entwurfsmusters
  • WebWorker kapselt JavaScript-Sandbox-Details

<<:  XHTML verwendet einige veraltete Elemente in HTML nicht mehr

>>:  3 Möglichkeiten, die maximale Anzahl von Verbindungen in MySQL richtig zu ändern

Artikel empfehlen

Verwendung der MySQL SHOW STATUS-Anweisung

Um die Leistung von MySQL anzupassen und den Dien...

Zabbix überwacht die Konfiguration der Docker-Anwendung

Der Einsatz von Containern kommt immer häufiger v...

Detaillierter Prozess der Installation von nginx1.9.1 auf centos8

1.17.9 Wirklich leckerer Nginx-Download-Adresse: ...

Util-Modul im node.js-Tutorial-Beispiel – detaillierte Erklärung

Inhaltsverzeichnis Ausgehend von der Typbeurteilu...

Eine gute Möglichkeit, Ihre Designfähigkeiten zu verbessern

Sogenanntes Talent (linke und rechte Gehirnhälfte...

Implementierung der Navigationsleiste und des Dropdown-Menüs in CSS

1. CSS-Navigationsleiste (1) Funktion der Navigat...

Analysieren des MySQL-Binärprotokolls

Inhaltsverzeichnis 1. Einführung in Binlog 2. Bin...