Cross-origin XMLHttpRequest

Normale webpagina's kunnen het XMLHttpRequest- object 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 . (Contentscripts zijn onderworpen aan CORB sinds Chrome 73 en CORS sinds Chrome 83. ) De oorsprong van extensies is niet zo beperkt: een script dat wordt uitgevoerd op de achtergrondpagina of het voorgrondtabblad van een extensie kan communiceren met externe servers buiten de oorsprong ervan, zolang de extensie vraagt ​​om cross-origin-machtigingen.

Oorsprong van de extensie

Elke actieve extensie bestaat binnen zijn eigen afzonderlijke beveiligingsoorsprong. Zonder extra rechten aan te vragen, kan de extensie XMLHttpRequest gebruiken 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:

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = handleStateChange; // Implemented elsewhere.
xhr.open("GET", chrome.extension.getURL('/config_resources/config.json'), true);
xhr.send();

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.

Cross-origin-machtigingen aanvragen

Door hosts of host-matchpatronen (of beide) toe te voegen aan de machtigingssectie van het manifestbestand , kan de extensie toegang vragen tot externe servers buiten de oorsprong.

{
  "name": "My extension",
  ...
  "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:

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

Beveiligingsoverwegingen

Het vermijden van cross-site scripting-kwetsbaarheden

Wanneer u bronnen gebruikt die via XMLHttpRequest zijn opgehaald, moet uw achtergrondpagina oppassen dat u niet het slachtoffer wordt van cross-site scripting . Vermijd in het bijzonder het gebruik van gevaarlijke API's, zoals de onderstaande:

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be evaluating an evil script!
    var resp = eval("(" + xhr.responseText + ")");
    ...
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be injecting a malicious script!
    document.getElementById("resp").innerHTML = xhr.responseText;
    ...
  }
}
xhr.send();

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

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // JSON.parse does not evaluate the attacker's scripts.
    var resp = JSON.parse(xhr.responseText);
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // innerText does not let the attacker inject HTML elements.
    document.getElementById("resp").innerText = xhr.responseText;
  }
}
xhr.send();

Beperking van 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. Eén (onveilige) benadering 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 aanpak 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 die meerdere bronnen bevatten.

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') {
        var 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.

Het aanpassen van het contentbeveiligingsbeleid

Als u het standaard inhoudsbeveiligingsbeleid voor apps of extensies 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.