Cross-origin XMLHttpRequest

Reguliere webpagina's kunnen het XMLHttpRequest- object gebruiken om gegevens van en naar 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 . (Contentscripts zijn sinds Chrome 73 onderworpen aan CORB en sinds Chrome 83 aan CORS .) Extensie-origins zijn niet zo beperkt: een script dat wordt uitgevoerd in de achtergrondpagina of het voorgrondtabblad van een extensie kan communiceren met externe servers buiten de eigen origin, zolang de extensie cross-origin-rechten aanvraagt.

Uitbreiding oorsprong

Elke actieve extensie bevindt zich in een eigen, afzonderlijke beveiligingsomgeving. Zonder extra privileges aan te vragen, kan de extensie XMLHttpRequest gebruiken 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:

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 beveiligingsbron dan zichzelf te gebruiken, bijvoorbeeld https://www.google.com, dan blokkeert de browser dit, tenzij de extensie de juiste toestemmingen voor toegang vanaf een andere oorsprong heeft aangevraagd.

Verzoek om toestemming voor toegang vanaf een andere oorsprong.

Door hosts of host-matchpatronen (of beide) toe te voegen aan het gedeelte met machtigingen in het manifestbestand , kan de extensie toegang aanvragen tot externe servers buiten de server waar deze vandaan komt.

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

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

Veiligheidsaspecten

Het voorkomen van cross-site scripting-kwetsbaarheden

Bij het gebruik van bronnen die via XMLHttpRequest worden opgehaald, moet uw achtergrondpagina ervoor zorgen dat deze geen slachtoffer wordt van cross-site scripting . Vermijd met name 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();

De toegang tot contentscripts beperken tot verzoeken van andere oorsprong.

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-verzoek uitvoert om een ​​content-script de prijs van een artikel te laten achterhalen. Een (onveilige) 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') {
        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 => ...);

HTTPS heeft de voorkeur 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.

Het contentbeveiligingsbeleid aanpassen

Als u het standaard Content Security Policy (CSP) voor apps of extensies 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.