Ursprungsübergreifende Netzwerkanfragen

Normale Webseiten können die fetch() oder XMLHttpRequest API verwenden, um Daten von Remoteservern zu senden und zu empfangen. Sie 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. Die Ursprünge von Erweiterungen sind nicht so beschränkt. Ein Skript, das in einem Erweiterungsdienst-Worker oder auf einem Tab im Vordergrund ausgeführt wird, kann mit Remoteservern außerhalb seines Ursprungs kommunizieren, solange die Erweiterung ursprungsübergreifende Berechtigungen anfordert.

Ursprung der Erweiterung

Jede ausgeführte Erweiterung hat einen eigenen Sicherheitsursprung. Die Erweiterung kann fetch() aufrufen, um Ressourcen innerhalb der Installation abzurufen, 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:

const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();

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 Sie Zugriff auf Remote-Server außerhalb des Ursprungs einer Erweiterung anfordern möchten, fügen Sie dem Abschnitt host_permissions der Manifestdatei Hosts, Übereinstimmungsmuster oder beides hinzu.

{
  "name": "My extension",
  ...
  "host_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:

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

Fetch() und XMLHttpRequest()

fetch() wurde speziell für Service Worker entwickelt und verfolgt einen breiteren Webtrend weg von synchronen Vorgängen. Die XMLHttpRequest() API wird in Erweiterungen außerhalb des Service Workers unterstützt. Durch ihren Aufruf wird der Abruf-Handler des Erweiterungsdienst-Workers ausgelöst. In neuen Arbeiten sollte nach Möglichkeit fetch() bevorzugt werden.

Sicherheitsaspekte

Cross-Site-Scripting-Sicherheitslücken vermeiden

Wenn du über fetch() abgerufene Ressourcen verwendest, solltest du darauf achten, dass dein nicht sichtbares Dokument, die Seitenleiste oder das Pop-up nicht Cross-Site-Scripting unterlaufen. Vermeiden Sie insbesondere die Verwendung gefährlicher APIs wie innerHTML. Beispiel:

const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = jsonData;
    ...

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

const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// JSON.parse does not evaluate the attacker's scripts.
let resp = JSON.parse(jsonData);

const response = await fetch("https://api.example.com/data.json");
const jsonData = response.json();
// textContent does not let the attacker inject HTML elements.
document.getElementById("resp").textContent = jsonData;

Content-Script-Zugriff 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 weniger sicherer 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') {
      const 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 Ihre Erweiterung durch Hinzufügen eines content_security_policy-Attributs zu Ihrem Manifest ändern, 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.