XMLHttpRequest multiorigine

Le pagine web normali possono utilizzare l'oggetto XMLHttpRequest per inviare e ricevere dati da server remoti, ma sono limitate dal criterio della stessa origine. Gli script di contenuti avviano le richieste per conto dell'origine web in cui sono stati inseriti e, pertanto, sono soggetti anche al criterio della stessa origine. (Gli script di contenuti sono soggetti a CORB da Chrome 73 e a CORS da Chrome 83.) Le origini delle estensioni non sono così limitate: uno script in esecuzione nella pagina di sfondo o nella scheda in primo piano di un'estensione può comunicare con server remoti al di fuori della sua origine, a condizione che l'estensione richieda le autorizzazioni cross-origin.

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, può recuperare i contenuti del file nel seguente 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 dalla propria, ad esempio https://www.google.com, il browser non lo consente a meno che l'estensione non abbia richiesto le autorizzazioni cross-origin appropriate.

Richiesta di autorizzazioni cross-origin

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 cross-origin possono essere nomi host completi, come i seguenti:

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

Oppure possono essere pattern di corrispondenza, come i seguenti:

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

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

Tieni presente, inoltre, che l'accesso viene concesso sia per host sia per schema. Se un'estensione vuole l'accesso HTTP sicuro e non sicuro a un determinato host o a un 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 le risorse recuperate tramite XMLHttpRequest, la pagina di sfondo deve fare attenzione a non essere vittima di cross-site scripting. In particolare, evita di utilizzare API pericolose come le seguenti:

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

In alternativa, 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 degli script di contenuti alle richieste cross-origin

Quando esegui richieste cross-origin per conto di uno script di contenuti, fai attenzione a proteggerti 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.

Considera un esempio in cui un'estensione esegue una richiesta multiorigine per consentire a uno script di contenuti di scoprire il prezzo di un articolo. Un approccio (non sicuro) consiste nel far sì che lo script di contenuti specifichi la risorsa esatta da recuperare dalla pagina di sfondo.

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 sopra riportato, lo script di contenuti può chiedere all'estensione di recuperare qualsiasi URL a cui l'estensione ha accesso. Una pagina web dannosa potrebbe essere in grado di falsificare questi messaggi e indurre l'estensione a concedere l'accesso alle risorse cross-origin.

In alternativa, progetta gestori di messaggi che limitino le risorse che possono essere recuperate. Di seguito, solo itemId viene fornito dallo script di contenuti 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 => ...);

Preferire HTTPS a HTTP

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

Modificare il criterio di sicurezza del contenuto

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