Ursprungsübergreifende Netzwerkanfragen

Normale Webseiten können die APIs fetch() oder XMLHttpRequest verwenden, um Daten von Remote-Servern zu senden und zu empfangen. Sie sind jedoch durch die Same-Origin-Policy eingeschränkt. Inhaltsscripts leiten Anfragen im Namen der Webquelle ein, in die das Inhaltsscript eingefügt wurde. Daher unterliegen Inhaltsscripts auch der Richtlinie zum gleichen Ursprung. Die Herkunft von Erweiterungen ist nicht so eingeschränkt. Ein Script, das in einem Erweiterungs-Dienst-Worker oder einem Tab im Vordergrund ausgeführt wird, kann mit Remote-Servern außerhalb des Ursprungs kommunizieren, sofern die Erweiterung plattformübergreifende Berechtigungen anfordert.

Erweiterungsquelle

Jede ausgeführte Erweiterung befindet sich in ihrem eigenen Sicherheitsherkunft. Ohne zusätzliche Berechtigungen kann die Erweiterung fetch() aufrufen, um Ressourcen innerhalb der Installation abzurufen. Wenn eine Erweiterung beispielsweise eine JSON-Konfigurationsdatei namens config.json im Ordner config_resources/ 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, 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 plattformübergreifenden Berechtigungen angefordert.

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 manifest Hosts, Übereinstimmungsmuster oder beides hinzu.

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

Werte für plattformübergreifende Berechtigungen können voll qualifizierte Hostnamen sein, z. B.:

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

Es können auch Übereinstimmungsmuster wie die folgenden sein:

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

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

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

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

Fetch() im Vergleich zu XMLHttpRequest()

fetch() wurde speziell für Service Worker entwickelt und folgt einem breiteren Webtrend weg von synchronen Vorgängen. Die XMLHttpRequest() API wird in Erweiterungen außerhalb des Service-Workers unterstützt. Wenn sie aufgerufen wird, wird der Abruf-Handler des Erweiterungs-Service-Workers ausgelöst. Bei neuer Arbeit sollte nach Möglichkeit fetch() verwendet werden.

Sicherheitsaspekte

Cross-Site-Scripting-Sicherheitslücken vermeiden

Wenn du Ressourcen verwendest, die über fetch() abgerufen wurden, solltest du darauf achten, dass dein nicht sichtbares Dokument, die Seitenleiste oder das Pop-up nicht dem Cross-Site-Scripting zum Opfer fallen. 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;
    ...

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

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;

Zugriff von Content-Scripts auf plattformübergreifende Anfragen beschränken

Wenn Sie plattformübergreifende Anfragen im Namen eines Inhaltsscripts ausführen, sollten Sie vor schädlichen Webseiten schützen, die versuchen könnten, sich als Inhaltsscript auszugeben. Insbesondere dürfen Inhaltsscripts keine beliebige URL anfordern.

Angenommen, eine Erweiterung führt eine plattformübergreifende Anfrage aus, damit ein Inhalts-Script den Preis eines Artikels ermitteln kann. Ein nicht so sicherer Ansatz wäre es, das Content-Skript 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())
);

Bei diesem Ansatz kann das Inhaltsskript die Erweiterung auffordern, eine beliebige URL abzurufen, auf die die Erweiterung Zugriff hat. Eine schädliche Webseite ist möglicherweise in der Lage, solche Nachrichten zu fälschen und die Erweiterung dazu zu bringen, Zugriff auf ursprungsübergreifende Ressourcen zu gewähren.

Entwerfen Sie stattdessen Nachrichten-Handler, die die abgerufenen Ressourcen einschränken. Unten wird nur die itemId vom Inhaltsskript bereitgestellt, nicht die vollständige URL.

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

Achten Sie außerdem besonders auf Ressourcen, die über HTTP abgerufen werden. Wenn Ihre Erweiterung in einem feindseligen Netzwerk verwendet wird, kann 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 Ihre Erweiterung ä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.