Cross-origin netwerkverzoeken

Normale webpagina's kunnen de fetch() of XMLHttpRequest API's gebruiken om gegevens te verzenden en te ontvangen van externe servers, maar ze worden beperkt door hetzelfde origin-beleid . Inhoudsscripts initiëren verzoeken namens de weboorsprong waarin het inhoudsscript is geïnjecteerd en daarom zijn inhoudsscripts ook onderworpen aan hetzelfde oorsprongsbeleid . De oorsprong van extensies is niet zo beperkt. Een script dat wordt uitgevoerd op een extensieservicewerknemer of op het voorgrondtabblad kan communiceren met externe servers buiten de oorsprong ervan, zolang de extensie maar om cross-origin-machtigingen vraagt.

Oorsprong van de extensie

Elke actieve extensie bestaat binnen zijn eigen afzonderlijke beveiligingsoorsprong. Zonder extra rechten aan te vragen, kan de extensie fetch() aanroepen om bronnen binnen de installatie op te halen. Als een extensie bijvoorbeeld een JSON-configuratiebestand met de naam config.json bevat, in een map config_resources/ , kan de extensie de inhoud van het bestand als volgt ophalen:

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

Als de extensie probeert een andere beveiligingsoorsprong dan zichzelf te gebruiken, bijvoorbeeld https://www.google.com, staat de browser dit niet toe, tenzij de extensie de juiste cross-origin-machtigingen heeft aangevraagd.

Vraag cross-origin-machtigingen aan

Als u toegang wilt aanvragen tot externe servers buiten de oorsprong van een extensie, voegt u hosts, match patterns of beide toe aan de sectie host_permissions van het manifestbestand .

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

Cross-originele machtigingswaarden kunnen volledig gekwalificeerde hostnamen zijn, zoals deze:

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

Of het kunnen matchpatronen zijn, zoals deze:

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

Een overeenkomstpatroon van 'https://*/' geeft HTTPS-toegang tot alle bereikbare domeinen. Houd er rekening mee dat de overeenkomstpatronen hier vergelijkbaar zijn met de overeenkomstpatronen van inhoudsscripts , maar dat alle padinformatie die de host volgt, wordt genegeerd.

Houd er ook rekening mee dat toegang zowel per host als per schema wordt verleend. Als een extensie zowel beveiligde als niet-beveiligde HTTP-toegang tot een bepaalde host of een reeks hosts wil, moet deze de machtigingen afzonderlijk declareren:

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

Fetch() versus XMLHttpRequest()

fetch() is speciaal gemaakt voor servicemedewerkers en volgt een bredere webtrend, weg van synchrone bewerkingen. De XMLHttpRequest() API wordt ondersteund in extensies buiten de service worker, en het aanroepen ervan activeert de ophaalhandler van de extensie service worker. Nieuw werk zou waar mogelijk de voorkeur moeten geven aan fetch() .

Beveiligingsoverwegingen

Vermijd cross-site scripting-kwetsbaarheden

Wanneer u bronnen gebruikt die zijn opgehaald via fetch() , moet u ervoor zorgen dat uw offscreen-document, zijpaneel of pop-up niet het slachtoffer wordt van cross-site scripting . Vermijd met name het gebruik van gevaarlijke API's zoals innerHTML . Bijvoorbeeld:

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;
    ...

Geef in plaats daarvan de voorkeur aan veiligere API's die geen scripts uitvoeren:

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;

Beperk de toegang tot inhoudsscripts tot cross-origineverzoeken

Wanneer u cross-origin-verzoeken namens een inhoudsscript uitvoert, moet u op uw hoede zijn voor kwaadwillende webpagina's die zouden kunnen proberen een inhoudsscript na te bootsen. Sta vooral niet toe dat inhoudsscripts een willekeurige URL opvragen.

Neem een ​​voorbeeld waarin een extensie een cross-origin-verzoek uitvoert om een ​​inhoudsscript de prijs van een item te laten ontdekken. Een niet zo veilige aanpak zou zijn om het inhoudsscript de exacte bron te laten specificeren die door de achtergrondpagina moet worden opgehaald.

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())
);

In de bovenstaande benadering kan het inhoudsscript de extensie vragen om elke URL op te halen waartoe de extensie toegang heeft. Een kwaadwillende webpagina kan dergelijke berichten mogelijk vervalsen en de extensie misleiden om toegang te geven tot bronnen van andere oorsprong.

Ontwerp in plaats daarvan berichthandlers die de bronnen beperken die kunnen worden opgehaald. Hieronder wordt alleen de itemId verstrekt door het inhoudsscript, en niet de volledige 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 => ...
);

Geef de voorkeur aan HTTPS boven HTTP

Wees bovendien vooral voorzichtig met bronnen die via HTTP worden opgehaald. Als uw extensie wordt gebruikt op een vijandig netwerk, kan een netwerkaanvaller (ook wel een "man-in-the-middle" genoemd) de reactie wijzigen en mogelijk uw extensie aanvallen. Geef in plaats daarvan waar mogelijk de voorkeur aan HTTPS.

Pas het inhoudbeveiligingsbeleid aan

Als u het standaard inhoudbeveiligingsbeleid voor uw extensie wijzigt door een content_security_policy -kenmerk aan uw manifest toe te voegen, moet u ervoor zorgen dat alle hosts waarmee u verbinding wilt maken, zijn toegestaan. Hoewel het standaardbeleid de verbindingen met hosts niet beperkt, moet u voorzichtig zijn als u expliciet de connect-src of default-src richtlijnen toevoegt.