XMLHttpRequest multiorigine

Le pagine web normali possono utilizzare l'oggetto XMLHttpRequest per inviare e ricevere dati dai server remoti, ma sono limitate dallo stesso criterio di origine. Gli script di contenuti avviano richieste per conto dell'origine web in cui è stato inserito lo script dei contenuti. Di conseguenza, anche gli script dei contenuti sono soggetti alle stesse norme di origine. Gli script di contenuti sono soggetti a CORB da Chrome 73 e CORS a partire da Chrome 83. Le origini delle estensioni non sono così limitate: uno script che viene eseguito nella pagina in background o nella scheda in primo piano di un'estensione può comunicare con server remoti al di fuori della sua origine, purché l'estensione richieda autorizzazioni multiorigine.

Origine estensione

Ogni estensione in esecuzione esiste all'interno della propria origine di sicurezza separata. Senza richiedere privilegi aggiuntivi, l'estensione può utilizzare XMLHttpRequest per ottenere risorse all'interno della sua installazione. Ad esempio, se un'estensione contiene un file di configurazione JSON denominato config.json, in una cartella config_resources, l'estensione può recuperare i contenuti del file in questo modo:

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

Se l'estensione tenta di utilizzare un'origine di sicurezza diversa da se stessa, ad esempio https://www.google.com, il browser non la consente, a meno che l'estensione non abbia richiesto le autorizzazioni multiorigine appropriate.

Richiesta di autorizzazioni multiorigine

Aggiungendo host o pattern di corrispondenza host (o entrambi) alla sezione delle autorizzazioni del file manifest, l'estensione può richiedere l'accesso a server remoti al di fuori della sua origine.

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

I valori delle autorizzazioni multiorigine possono essere nomi host completi, come i seguenti:

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

In alternativa, possono essere utilizzati pattern di corrispondenza, ad esempio:

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

Un pattern di corrispondenza di "https://*/" consente l'accesso HTTPS a tutti i domini raggiungibili. Tieni presente che in questo caso i pattern di corrispondenza sono simili ai pattern di corrispondenza degli script dei contenuti, ma qualsiasi informazione sul percorso che segue l'host viene ignorata.

Tieni inoltre presente che l'accesso viene concesso sia dall'host sia dallo schema. Se un'estensione desidera l'accesso HTTP sicuro e non protetto a un determinato host o insieme di host, deve dichiarare le autorizzazioni separatamente:

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

Considerazioni sulla sicurezza

Evitare le vulnerabilità di cross-site scripting

Quando utilizzi risorse recuperate tramite XMLHttpRequest, la tua pagina di background deve fare attenzione a non cadere vittima di cross-site scripting. In particolare, evita di utilizzare API pericolose come:

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

Preferisci API più sicure che non eseguono script:

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

Limitare l'accesso agli script dei contenuti alle richieste multiorigine

Quando esegui richieste multiorigine per conto di uno script di contenuti, presta attenzione a guardarti da pagine web dannose che potrebbero tentare di impersonare uno script di contenuti. In particolare, non consentire agli script di contenuti di richiedere un URL arbitrario.

Prendiamo come esempio un'estensione in cui un'estensione esegue una richiesta multiorigine per consentire a uno script di contenuti di rilevare il prezzo di un elemento. Un approccio (non sicuro) prevede che lo script dei contenuti specifichi la risorsa esatta che deve essere recuperata dalla pagina in background.

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

Nell'approccio precedente, lo script dei contenuti può chiedere all'estensione di recuperare qualsiasi URL a cui ha accesso. Una pagina web dannosa potrebbe essere in grado di falsificare questi messaggi e indurre con l'inganno l'estensione a concedere l'accesso alle risorse multiorigine.

Progetta invece gestori di messaggi che limitano le risorse che possono essere recuperate. Di seguito, lo script dei contenuti fornisce solo il itemId e non l'URL completo.

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

Preferisco HTTPS a HTTP

Inoltre, fai particolare attenzione alle risorse recuperate tramite HTTP. Se la tua estensione viene utilizzata su una rete ostile, un utente malintenzionato di rete (noto anche come "man-in-the-middle") potrebbe modificare la risposta e, potenzialmente, attaccare l'estensione. Se possibile, preferisci utilizzare HTTPS.

Modificare i criteri di sicurezza dei contenuti

Se modifichi i Criteri di sicurezza del contenuto predefiniti per app o estensioni aggiungendo un attributo content_security_policy al file manifest, dovrai assicurarti che tutti gli host a cui vuoi connetterti siano consentiti. Anche se il criterio predefinito non limita le connessioni agli host, fai attenzione quando aggiungi esplicitamente le istruzioni connect-src o default-src.