eval() in iFrames verwenden, die in einer Sandbox ausgeführt werden

Das Erweiterungssystem von Chrome erzwingt eine relativ strikte standardmäßige Content Security Policy (CSP). Die Richtlinieneinschränkungen sind einfach: Das Skript muss in separate JavaScript-Dateien müssen Inline-Event-Handler zur Verwendung von addEventListener konvertiert werden und eval() ist deaktiviert.

Wir wissen jedoch, dass eine Vielzahl von Bibliotheken eval()- und eval-ähnliche Konstrukte verwendet, z. B.: new Function() für die Leistungsoptimierung und einen einfachen Ausdruck. Vorlagenbibliotheken sind besonders anfällig für diese Art der Implementierung. Einige Frameworks (z. B. Angular.js) unterstützen CSP standardmäßig, viele beliebte Frameworks wurden jedoch noch nicht auf einen Mechanismus umgestellt, der mit der eval-freien Welt von Erweiterungen kompatibel ist. Es hat sich daher als besser erwiesen, dass diese Funktion nicht mehr unterstützt wird. problematisch als erwartet.

In diesem Dokument wird die sichere Sandbox-Technologie vorgestellt, mit der Sie diese Bibliotheken in Ihre Projekte einbinden können. ohne Abstriche bei der Sicherheit machen zu müssen.

Warum eine Sandbox?

eval ist innerhalb einer Erweiterung gefährlich, da der von ihm ausgeführte Code Zugriff auf alles im Erweiterung mit umfangreichen Berechtigungen. Es stehen zahlreiche leistungsstarke chrome.* APIs zur Verfügung, die die Sicherheit und den Datenschutz eines Nutzers erheblich beeinträchtigen; Eine einfache Daten-Exfiltration ist für uns am wenigsten sorgen. Die angebotene Lösung ist eine Sandbox, in der eval Code ausführen kann, ohne auf die Daten oder die wertvollen APIs der Erweiterung zuzugreifen. Keine Daten, keine APIs, kein Problem.

Dies erreichen wir, indem wir bestimmte HTML-Dateien im Erweiterungspaket als Sandbox-Technologie auflisten. Wenn eine in einer Sandbox ausgeführte Seite geladen wird, wird sie an einen eindeutigen Ursprung verschoben und abgelehnt. Zugriff auf chrome.* APIs Wenn wir diese in einer Sandbox ausgeführte Seite über iframe in unsere Erweiterung laden, kann es die Nachrichten weiterleiten, es auf diese Nachrichten reagieren und darauf warten, dass es uns ein Ergebnis. Dieser einfache Messaging-Mechanismus bietet uns alles, was wir brauchen, um eval-basierten Code sicher in den Workflow unserer Erweiterung einzubinden.

Sandbox erstellen und verwenden

Wenn Sie direkt in Code einsteigen möchten, nehmen Sie die Sandboxing-Beispielerweiterung deaktiviert. Es ist ein funktionierendes Beispiel für eine winzige Messaging-API, die auf dem Handlebars basiert. Vorlagenbibliothek, die Ihnen alles bietet, was Sie für den Einstieg benötigen. Für diejenigen, die noch etwas mehr Erklärung benötigen, gehen wir das Beispiel hier gemeinsam durch.

Dateien im Manifest auflisten

Jede Datei, die in einer Sandbox ausgeführt werden soll, muss im Erweiterungsmanifest durch Hinzufügen eines sandbox-Property. Dieser Schritt ist wichtig und kann leicht vergessen werden. Prüfen Sie daher noch einmal, ob Ihre Datei in der Sandbox im Manifest aufgeführt ist. In diesem Beispiel wird die Datei geschickt in einer Sandbox ausgeführt. namens "sandbox.html". Der Manifesteintrag sieht so aus:

{
  ...,
  "sandbox": {
     "pages": ["sandbox.html"]
  },
  ...
}

Datei aus der Sandbox laden

Um mit der in der Sandbox ausgeführten Datei etwas Interessantes zu tun, müssen wir sie in einem Kontext laden, in dem kann der Code der Erweiterung berücksichtigt werden. Hier wurde sandbox.html in Erweiterungsseite über iframe. Die JavaScript-Datei der Seite enthält Code, der jedes Mal, wenn auf die Browseraktion geklickt wird, eine Nachricht an die Sandbox sendet. Dazu wird die iframe auf der Seite gefunden und postMessage() über ihre contentWindow aufgerufen. Die Nachricht ist ein Objekt mit drei Eigenschaften: context, templateName und command. context und command werden wir gleich genauer betrachten.

service-worker.js:

chrome.action.onClicked.addListener(() => {
  chrome.tabs.create({
    url: 'mainpage.html'
  });
  console.log('Opened a tab with a sandboxed page!');
});

extension-page.js:

let counter = 0;
document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('reset').addEventListener('click', function () {
    counter = 0;
    document.querySelector('#result').innerHTML = '';
  });

  document.getElementById('sendMessage').addEventListener('click', function () {
    counter++;
    let message = {
      command: 'render',
      templateName: 'sample-template-' + counter,
      context: { counter: counter }
    };
    document.getElementById('theFrame').contentWindow.postMessage(message, '*');
  });

Gefährliche Handlungen

Wenn sandbox.html geladen wird, wird auch die Handlebars-Bibliothek geladen. Anschließend wird eine Inline-Vorlage erstellt und kompiliert, wie von Handlebars vorgeschlagen:

extension-page.html:

<!DOCTYPE html>
<html>
  <head>
    <script src="mainpage.js"></script>
    <link href="styles/main.css" rel="stylesheet" />
  </head>
  <body>
    <div id="buttons">
      <button id="sendMessage">Click me</button>
      <button id="reset">Reset counter</button>
    </div>

    <div id="result"></div>

    <iframe id="theFrame" src="sandbox.html" style="display: none"></iframe>
  </body>
</html>

sandbox.html:

   <script id="sample-template-1" type="text/x-handlebars-template">
      <div class='entry'>
        <h1>Hello</h1>
        <p>This is a Handlebar template compiled inside a hidden sandboxed
          iframe.</p>
        <p>The counter parameter from postMessage() (outer frame) is:
          </p>
      </div>
    </script>

    <script id="sample-template-2" type="text/x-handlebars-template">
      <div class='entry'>
        <h1>Welcome back</h1>
        <p>This is another Handlebar template compiled inside a hidden sandboxed
          iframe.</p>
        <p>The counter parameter from postMessage() (outer frame) is:
          </p>
      </div>
    </script>

Das funktioniert immer! Handlebars.compile verwendet zwar letztendlich new Function, aber es funktioniert und wir haben eine kompilierte Vorlage in templates['hello'].

Ergebnis zurückgeben

Wir stellen diese Vorlage zur Verwendung zur Verfügung, indem wir einen Nachrichten-Listener einrichten, der Befehle akzeptiert über die Erweiterungsseite. Anhand der übergebenen command wird bestimmt, was getan werden soll. Sie könnten sich vorstellen, mehr als nur zu rendern, z. B. Vorlagen zu erstellen. Vielleicht verwalten Sie sie und die context wird zum Rendern direkt an die Vorlage übergeben. Der gerenderte HTML-Code wird an die Erweiterungsseite zurückgegeben, damit die Erweiterung damit später etwas Nützliches tun kann:

 <script>
      const templatesElements = document.querySelectorAll(
        "script[type='text/x-handlebars-template']"
      );
      let templates = {},
        source,
        name;

      // precompile all templates in this page
      for (let i = 0; i < templatesElements.length; i++) {
        source = templatesElements[i].innerHTML;
        name = templatesElements[i].id;
        templates[name] = Handlebars.compile(source);
      }

      // Set up message event handler:
      window.addEventListener('message', function (event) {
        const command = event.data.command;
        const template = templates[event.data.templateName];
        let result = 'invalid request';

       // if we don't know the templateName requested, return an error message
        if (template) {
          switch (command) {
            case 'render':
              result = template(event.data.context);
              break;
            // you could even do dynamic compilation, by accepting a command
            // to compile a new template instead of using static ones, for example:
            // case 'new':
            //   template = Handlebars.compile(event.data.templateSource);
            //   result = template(event.data.context);
            //   break;
              }
        } else {
            result = 'Unknown template: ' + event.data.templateName;
        }
        event.source.postMessage({ result: result }, event.origin);
      });
    </script>

Auf der Seite der Erweiterung erhalten wir diese Nachricht und können etwas Interessantes mit den übergebenen html-Daten tun. In diesem Fall geben wir es einfach in einer Benachrichtigung wieder, aber kann dieser HTML-Code sicher als Teil der Benutzeroberfläche der Erweiterung verwendet werden. Einfügen über innerHTML stellt kein erhebliches Sicherheitsrisiko dar, da wir den gerenderten Inhalten vertrauen Sandbox ausführen.

Dieser Mechanismus vereinfacht die Erstellung von Vorlagen, ist aber natürlich nicht darauf beschränkt. Beliebig Code, der gemäß einer strengen Content Security Policy nicht sofort funktioniert, kann in einer Sandbox ausgeführt werden. in Es ist oft nützlich, die Komponenten der Erweiterungen in einer Sandbox auszuführen, die korrekt ausgeführt werden. jedes Teil Ihres Programms auf die kleinstmöglichen Berechtigungen zu beschränken, die erforderlich sind, ordnungsgemäß ausführen. Google-Präsentation Write Secure Web Apps and Chrome Extensions Auf der I/O 2012 finden Sie einige gute Beispiele für diese Techniken in der Praxis. .