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. Content-Skripts initiieren Anfragen
im Namen des Web-Ursprungs, in den das Content-Skript eingefügt wurde. Daher unterliegen Content
Skripts auch der Same-Origin-Policy. Erweiterungsursprünge sind nicht so eingeschränkt. Ein Skript
das in einem Erweiterungs-Service-Worker oder im Vordergrundtab ausgeführt wird, kann mit Remote-Servern außerhalb von
seinem Ursprung kommunizieren, sofern die Erweiterung Hostberechtigungen anfordert.
Erweiterungsursprung
Jede ausgeführte Erweiterung befindet sich in einem eigenen Sicherheitsursprung. Ohne zusätzliche Berechtigungen anzufordern, kann die Erweiterung fetch() aufrufen, 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:
const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();
Wenn die Erweiterung versucht, Inhalte von einem anderen Sicherheitsursprung als ihrem eigenen anzufordern, z. B. von https://www.google.com, wird dies als eine ursprungsübergreifende Anfrage behandelt, es sei denn, die Erweiterung hat Hostberechtigungen. Cross-Origin-Anfragen werden in Content-Skripts immer als solche behandelt, auch wenn die Erweiterung Hostberechtigungen hat.
Cross-Origin-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 Datei manifest Hosts, Übereinstimmungsmuster, oder beides hinzu.
{
"name": "My extension",
...
"host_permissions": [
"https://www.google.com/"
],
...
}
Werte für Cross-Origin-Berechtigungen können vollqualifizierte Hostnamen sein, z. B.:
- "https://www.google.com/"
- "https://www.gmail.com/"
Oder sie können Übereinstimmungsmuster sein, z. B.:
- "https://*.google.com/"
- "https://*/"
Ein Übereinstimmungsmuster von „https://*/“ ermöglicht HTTPS-Zugriff auf alle erreichbaren Domains. Hier sind die Übereinstimmungsmuster ähnlich wie die Übereinstimmungsmuster für Content-Skripts, aber alle Pfadinformationen nach dem Host werden ignoriert.
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, muss sie die Berechtigungen separat deklarieren:
"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 Web-Trend weg von synchronen Vorgängen. Die XMLHttpRequest() API wird in Erweiterungen außerhalb des Service-Workers unterstützt und durch den Aufruf wird der Fetch-Handler des Erweiterungs-Service-Workers ausgelöst. Bei neuen Projekten sollte nach Möglichkeit fetch() verwendet werden.
Sicherheitsaspekte
Cross-Site-Scripting-Schwachstellen vermeiden
Wenn Sie Ressourcen verwenden, die über fetch() abgerufen wurden, sollten Sie darauf achten, dass Ihr Offscreen-Dokument, Ihre Seitenleiste oder Ihr Pop-up nicht
Opfer von Cross-Site-Scripting wird. 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 Skripts 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-Skripts auf Cross-Origin-Anfragen beschränken
Wenn Sie Cross-Origin-Anfragen im Namen eines Content-Skripts ausführen, sollten Sie sich vor schädlichen Webseiten schützen, die versuchen könnten, ein Content-Skript zu imitieren. Erlauben Sie Content-Skripts insbesondere nicht, eine beliebige URL anzufordern.
Stellen Sie sich ein Beispiel vor, bei dem eine Erweiterung eine ursprungsübergreifende Anfrage ausführt, damit ein Content-Skript den Preis eines Artikels ermitteln kann. Ein nicht so sicherer Ansatz wäre, 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 Content-Skript 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 Cross-Origin-Ressourcen zu gewähren.
Entwerfen Sie stattdessen Nachrichtenhandler, die die abrufbaren Ressourcen einschränken. Unten wird nur die itemId vom Content-Skript 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
Seien Sie außerdem besonders vorsichtig bei Ressourcen, die über HTTP abgerufen werden. Wenn Ihre Erweiterung in einem feindseligen Netzwerk verwendet wird, kann ein Netzwerkangreifer (ein sogenannter "man-in-the-middle") die Antwort ändern und möglicherweise Ihre Erweiterung angreifen. Verwenden Sie stattdessen nach Möglichkeit immer HTTPS.
Content Security Policy anpassen
Wenn Sie die Standard-Content Security Policy für Ihre Erweiterung ändern, indem Sie Ihrem Manifest das
content_security_policy Attribut hinzufügen, müssen Sie dafür sorgen, dass alle Hosts, mit denen
Sie eine Verbindung herstellen möchten, zulässig sind. Die Standardrichtlinie schränkt Verbindungen zu Hosts nicht ein. Seien Sie jedoch vorsichtig, wenn Sie die Direktiven connect-src oder default-src explizit hinzufügen.