Ursprungsübergreifende XMLHttpRequest-Anfrage

Normale Webseiten können das Objekt XMLHttpRequest verwenden, um Daten von Remoteservern zu senden und von ihnen zu empfangen. Diese sind jedoch durch dieselbe Ursprungsrichtlinie eingeschränkt. Inhaltsskripts initiieren Anfragen im Namen des Webursprungs, in den das Inhaltsskript eingeschleust wurde. Daher unterliegen Inhaltsskripte ebenfalls derselben Ursprungsrichtlinie. Inhaltsskripts unterliegen CORB seit Chrome 73 und CORS seit Chrome 83. Die Ursprünge von Erweiterungen sind nicht beschränkt. Ein Skript, das auf der Hintergrundseite oder dem Tab im Vordergrund einer Erweiterung ausgeführt wird, kann mit Remoteservern außerhalb ihres Ursprungs kommunizieren, solange die Erweiterung ursprungsübergreifende Berechtigungen anfordert.

Ursprung der Erweiterung

Jede ausgeführte Erweiterung hat einen eigenen Sicherheitsursprung. Die Erweiterung kann dann mit XMLHttpRequest Ressourcen innerhalb der Installation abrufen, ohne zusätzliche Berechtigungen anzufordern. Wenn eine Erweiterung beispielsweise eine JSON-Konfigurationsdatei namens config.json in einem config_resources-Ordner 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, eine andere Sicherheitsquelle als sich selbst zu verwenden, z. B. https://www.google.com, lässt der Browser dies nur zu, wenn die Erweiterung die entsprechenden ursprungsübergreifenden Berechtigungen angefordert hat.

Ursprungsübergreifende Berechtigungen anfordern

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

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

Ursprungsübergreifende Berechtigungswerte können voll qualifizierte Hostnamen wie die folgenden sein:

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

Es kann sich auch um Übereinstimmungsmuster wie diese handeln:

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

Bei einem Übereinstimmungsmuster wie „https://*/“ ist der HTTPS-Zugriff auf alle erreichbaren Domains möglich. Übereinstimmungsmuster sind hier ähnlich wie Übereinstimmungsmuster für Content-Skripts, es werden jedoch alle Pfadinformationen, die auf den Host folgen, ignoriert.

Beachten Sie auch, dass der Zugriff sowohl vom Host als auch durch das Schema gewährt wird. Wenn eine Erweiterung sowohl sicheren als auch nicht sicheren HTTP-Zugriff auf einen bestimmten Host oder eine Gruppe von Hosts wünscht, muss sie die Berechtigungen separat deklarieren:

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

Sicherheitsaspekte

Cross-Site-Scripting-Sicherheitslücken vermeiden

Wenn Sie über XMLHttpRequest abgerufene Ressourcen verwenden, achten Sie darauf, dass Ihre Hintergrundseite nicht Cross-Site-Scripting unterliegt. Vermeiden Sie insbesondere die Verwendung gefährlicher APIs wie die 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();

Stattdessen sollten Sie sicherere APIs bevorzugen, bei denen keine Skripts ausgeführt werden:

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 auf Content-Skripts auf ursprungsübergreifende Anfragen beschränken

Wenn Sie ursprungsübergreifende Anfragen im Namen eines Inhaltsskripts ausführen, achten Sie darauf, vor schädlichen Webseiten zu schützen, die versuchen könnten, die Identität eines Inhaltsskripts zu stehlen. Insbesondere dürfen Content-Skripts keine beliebige URL anfordern.

Nehmen wir ein Beispiel, bei dem eine Erweiterung eine ursprungsübergreifende Anfrage durchführt, damit ein Inhaltsskript den Preis eines Artikels ermitteln kann. Ein (unsicherer) Ansatz wäre, das Inhaltsskript die genaue Ressource angeben zu lassen, 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()));

Im obigen Ansatz kann das Inhaltsskript die Erweiterung auffordern, jede URL abzurufen, auf die die Erweiterung zugreifen kann. Eine schädliche Webseite kann solche Nachrichten möglicherweise fälschen und die Erweiterung so täuschen, dass sie Zugriff auf ursprungsübergreifende Ressourcen gewährt.

Erstellen Sie stattdessen Nachrichten-Handler, die die abzurufenden Ressourcen beschränken. Unten wird nur die itemId vom Content-Skript und nicht die vollständige URL bereitgestellt.

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 gegenüber HTTP bevorzugen

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

Content Security Policy anpassen

Wenn Sie die standardmäßige 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. Obwohl die Standardrichtlinie Verbindungen zu Hosts nicht einschränkt, sollten Sie vorsichtig sein, wenn Sie die Anweisungen connect-src oder default-src explizit hinzufügen.