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 initiieren Anfragen im Namen der Webquelle, 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 Hostberechtigungen anfordert.

Erweiterungsquelle

Jede laufende Erweiterung hat einen eigenen Sicherheitsursprung. Ohne zusätzliche Berechtigungen kann die Erweiterung fetch() aufrufen, um Ressourcen innerhalb ihrer 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, Inhalte von einem anderen Sicherheitsursprung als dem eigenen anzufordern, z. B. von https://www.google.com, wird dies als mehrfacher Ursprung behandelt, es sei denn, die Erweiterung hat Hostberechtigungen. Anfragen zwischen verschiedenen Ursprüngen werden in Inhaltsscripts immer als solche behandelt, auch wenn die Erweiterung Hostberechtigungen 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 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 nach Host als auch nach 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 Sie über fetch() abgerufene Ressourcen verwenden, sollten Sie darauf achten, dass Ihr Offscreen-Dokument, Ihre Seitenleiste oder Ihr Pop-up nicht Opfer von Cross-Site-Scripting werden. 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. Eine nicht so sichere Methode wäre, im Inhaltsskript die genaue Ressource anzugeben, 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 kann solche Nachrichten fälschen und die Erweiterung dazu bringen, Zugriff auf plattformü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 als Präferenz gegenüber HTTP

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.