Cross-origin netwerkverzoeken

Reguliere webpagina's kunnen de fetch() of XMLHttpRequest API's gebruiken om gegevens van externe servers te verzenden en te ontvangen, maar ze zijn gebonden aan het same-originbeleid . Contentscripts initiëren verzoeken namens de weborigin waarin het contentscript is geïnjecteerd en zijn daarom ook onderworpen aan het same-originbeleid . Extensie-origins zijn niet zo beperkt. Een script dat wordt uitgevoerd in een extensieserviceworker of op een voorgrondtabblad kan communiceren met externe servers buiten de eigen origin, zolang de extensie maar hostrechten aanvraagt.

Uitbreiding oorsprong

Elke actieve extensie bevindt zich in een eigen, afzonderlijke beveiligingsomgeving. Zonder extra privileges aan te vragen, kan de extensie de functie fetch() aanroepen om resources binnen de installatie op te halen. Als een extensie bijvoorbeeld een JSON-configuratiebestand met de naam ` config.json in een map config_resources/ bevat, 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 content op te vragen van een andere beveiligingsbron dan de eigen, bijvoorbeeld https://www.google.com, wordt dit behandeld als een cross-origin request, tenzij de extensie hostrechten heeft. Cross-origin requests worden in content scripts altijd als zodanig behandeld, zelfs als de extensie hostrechten heeft.

Verzoek om toestemming voor toegang vanaf een andere oorsprong.

Om toegang te vragen tot externe servers buiten de oorsprong van een extensie, voegt u hosts, matchpatronen of beide toe aan de sectie host_permissions van het manifestbestand .

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

Toegangswaarden voor meerdere bronnen kunnen volledig gekwalificeerde hostnamen zijn, zoals deze:

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

Of het kunnen overeenkomende patronen zijn, zoals deze:

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

Een matchpatroon van "https://*/" staat HTTPS-toegang toe tot alle bereikbare domeinen. Merk op dat matchpatronen hier vergelijkbaar zijn met matchpatronen voor contentscripts , maar dat alle padinformatie na de host 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() -methode is specifiek ontwikkeld voor service workers en volgt een bredere webtrend om synchrone bewerkingen te vermijden. De XMLHttpRequest() -API wordt ondersteund in extensies buiten de service worker, en het aanroepen ervan activeert de `fetch`-handler van de extensie-service worker. Bij nieuwe ontwikkelingen zou fetch() zoveel mogelijk de voorkeur moeten krijgen.

Veiligheidsaspecten

Vermijd cross-site scripting-kwetsbaarheden.

Bij het gebruik van resources die via fetch() worden opgehaald, moet uw offscreen-document, zijpaneel of pop-up ervoor zorgen dat het geen 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 contentscripts tot cross-origin-verzoeken.

Wees voorzichtig bij het uitvoeren van cross-origin-verzoeken namens een contentscript en bescherm uzelf tegen kwaadwillende webpagina's die zich mogelijk voordoen als een contentscript. Sta in het bijzonder niet toe dat contentscripts een willekeurige URL opvragen.

Neem bijvoorbeeld een situatie waarin een extensie een cross-origin request uitvoert om een ​​content script de prijs van een artikel te laten achterhalen. Een minder veilige aanpak zou zijn om het content script 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())
);

Bij de bovenstaande aanpak kan het content-script de extensie vragen om elke URL op te halen waartoe de extensie toegang heeft. Een kwaadwillende webpagina kan dergelijke berichten vervalsen en de extensie misleiden om toegang te verlenen tot bronnen van een andere oorsprong.

Ontwerp in plaats daarvan berichtafhandelaars die de op te halen bronnen beperken. Hieronder wordt alleen de itemId door het contentscript verstrekt, 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 extra voorzichtig met bronnen die via HTTP worden opgehaald. Als uw extensie wordt gebruikt op een onveilig netwerk, kan een netwerkaanvaller (ook wel een "man-in-the-middle" genoemd) het antwoord wijzigen en mogelijk uw extensie aanvallen. Geef daarom waar mogelijk de voorkeur aan HTTPS.

Pas het inhoudsbeveiligingsbeleid aan.

Als u het standaard Content Security Policy (CSP) voor uw extensie wijzigt door een ` content_security_policy attribuut aan uw manifest toe te voegen, moet u ervoor zorgen dat alle hosts waarmee u verbinding wilt maken, zijn toegestaan. Hoewel het standaardbeleid verbindingen met hosts niet beperkt, moet u voorzichtig zijn bij het expliciet toevoegen van de connect-src of default-src directives.