Ursprungsübergreifende XMLHttpRequest-Anfrage

Auf normalen Webseiten kann das XMLHttpRequest-Objekt verwendet werden, um Daten von Remote-Servern zu senden und zu empfangen. Allerdings unterliegen sie der Same-Origin-Policy. Content-Skripte initiieren Anfragen im Namen des Webursprungs, in den das Content-Skript eingefügt wurde. Daher unterliegen Content-Skripte auch der Same-Origin-Policy. (Content-Scripts unterliegen CORB seit Chrome 73 und CORS seit Chrome 83.) Erweiterungsursprünge sind nicht so eingeschränkt. Ein Skript, das auf der Hintergrundseite oder dem Vordergrundtab einer Erweiterung ausgeführt wird, kann mit Remoteservern außerhalb seines Ursprungs kommunizieren, sofern die Erweiterung ursprungsübergreifende Berechtigungen anfordert.

Erweiterungsursprung

Jede ausgeführte Erweiterung hat ihren eigenen Sicherheitsursprung. Ohne zusätzliche Berechtigungen anzufordern, kann die Erweiterung XMLHttpRequest verwenden, um Ressourcen innerhalb ihrer Installation abzurufen. Wenn eine Erweiterung beispielsweise eine JSON-Konfigurationsdatei mit dem Namen config.json in einem Ordner config_resources enthält, kann die Erweiterung den Inhalt der Datei so abrufen:

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = handleStateChange; // Implemented elsewhere.
xhr.open("GET", chrome.extension.getURL('/config_resources/config.json'), true);
xhr.send();

Wenn die Erweiterung versucht, einen anderen Sicherheitsursprung als sich selbst zu verwenden, z. B. https://www.google.com, lässt der Browser dies nicht zu, es sei denn, die Erweiterung hat die entsprechenden ursprungsübergreifenden Berechtigungen angefordert.

Ursprungsübergreifende Berechtigungen anfordern

Wenn Sie dem Abschnitt permissions der Manifestdatei Hosts oder Host-Übereinstimmungsmuster (oder beides) hinzufügen, kann die Erweiterung Zugriff auf Remote-Server außerhalb ihres Ursprungs anfordern.

{
  "name": "My extension",
  ...
  "permissions": [
    "https://www.google.com/"
  ],
  ...
}

Werte für bereichsübergreifende Berechtigungen können voll qualifizierte Hostnamen wie die folgenden sein:

  • "https://www.google.com/"
  • "https://www.gmail.com/"

Sie können auch Muster für den Abgleich sein, wie diese:

  • "https://*.google.com/"
  • "https://*/"

Mit dem Muster „https://*/“ wird HTTPS-Zugriff auf alle erreichbaren Domains zugelassen. Beachten Sie, dass die Übereinstimmungsmuster hier Übereinstimmungsmustern für Content-Scripts ähneln, aber alle Pfadinformationen nach dem Host ignoriert werden.

Beachten Sie auch, dass der Zugriff sowohl nach Host als auch nach Schema gewährt wird. Wenn eine Erweiterung sowohl sicheren als auch nicht sicheren HTTP-Zugriff auf einen bestimmten Host oder eine Gruppe von Hosts benötigt, müssen die Berechtigungen separat deklariert werden:

"permissions": [
  "http://www.google.com/",
  "https://www.google.com/"
]

Sicherheitsaspekte

Cross-Site-Scripting-Sicherheitslücken vermeiden

Wenn Sie Ressourcen verwenden, die über XMLHttpRequest abgerufen werden, sollte Ihre Hintergrundseite nicht Opfer von Cross-Site-Scripting werden. Vermeiden Sie insbesondere die Verwendung gefährlicher APIs wie der folgenden:

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be evaluating an evil script!
    var resp = eval("(" + xhr.responseText + ")");
    ...
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be injecting a malicious script!
    document.getElementById("resp").innerHTML = xhr.responseText;
    ...
  }
}
xhr.send();

Verwenden Sie stattdessen sicherere APIs, die keine Skripts ausführen:

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // JSON.parse does not evaluate the attacker's scripts.
    var resp = JSON.parse(xhr.responseText);
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // innerText does not let the attacker inject HTML elements.
    document.getElementById("resp").innerText = xhr.responseText;
  }
}
xhr.send();

Zugriff von Content-Scripts auf ursprungsübergreifende Anfragen einschränken

Wenn Sie im Namen eines Content-Scripts ursprungsübergreifende Anfragen ausführen, sollten Sie Vorsichtsmaßnahmen gegen schädliche Webseiten treffen, die versuchen, ein Content-Script zu imitieren. Insbesondere dürfen Content-Scripts keine beliebige URL anfordern.

Stellen Sie sich beispielsweise vor, dass eine Erweiterung eine ursprungsübergreifende Anfrage ausführt, damit ein Content-Script den Preis eines Artikels ermitteln kann. Ein (unsicherer) Ansatz wäre, dass das Content-Script die genaue Ressource angibt, die von der Hintergrundseite abgerufen werden soll.

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
      if (request.contentScriptQuery == 'fetchUrl') {
        // WARNING: SECURITY PROBLEM - a malicious web page may abuse
        // the message handler to get access to arbitrary cross-origin
        // resources.
        fetch(request.url)
            .then(response => response.text())
            .then(text => sendResponse(text))
            .catch(error => ...)
        return true;  // Will respond asynchronously.
      }
    });
chrome.runtime.sendMessage(
    {contentScriptQuery: 'fetchUrl',
     url: 'https://another-site.com/price-query?itemId=' +
              encodeURIComponent(request.itemId)},
    response => parsePrice(response.text()));

Bei der oben beschriebenen Methode kann das Inhaltskript die Erweiterung auffordern, eine beliebige URL abzurufen, auf die die Erweiterung Zugriff hat. Eine schädliche Webseite kann solche Nachrichten möglicherweise fälschen und die Erweiterung dazu bringen, Zugriff auf ursprungsübergreifende Ressourcen zu gewähren.

Entwerfen Sie stattdessen Nachrichtentypen, die die abrufbaren Ressourcen einschränken. Unten wird nur die itemId vom Content-Script bereitgestellt, nicht die vollständige URL.

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
      if (request.contentScriptQuery == 'queryPrice') {
        var url = 'https://another-site.com/price-query?itemId=' +
            encodeURIComponent(request.itemId);
        fetch(url)
            .then(response => response.text())
            .then(text => parsePrice(text))
            .then(price => sendResponse(price))
            .catch(error => ...)
        return true;  // Will respond asynchronously.
      }
    });
chrome.runtime.sendMessage(
    {contentScriptQuery: 'queryPrice', itemId: 12345},
    price => ...);

HTTPS als Präferenz gegenüber HTTP

Seien Sie außerdem besonders vorsichtig bei Ressourcen, die über HTTP abgerufen werden. Wenn Ihre Erweiterung in einem feindseligen Netzwerk verwendet wird, könnte ein Netzwerkangreifer (auch "man-in-the-middle" genannt) die Antwort ändern und möglicherweise Ihre Erweiterung angreifen. Verwenden Sie stattdessen nach Möglichkeit HTTPS.

Content Security Policy anpassen

Wenn Sie die Standard-Content Security Policy für Apps oder Erweiterungen ändern, indem Sie Ihrem Manifest ein content_security_policy-Attribut hinzufügen, müssen Sie dafür sorgen, dass alle Hosts, zu denen Sie eine Verbindung herstellen möchten, zulässig sind. Die Standardrichtlinie schränkt zwar keine Verbindungen zu Hosts ein, Sie sollten jedoch vorsichtig sein, wenn Sie die Anweisungen connect-src oder default-src explizit hinzufügen.