XMLHttpRequest multiorigine

Le pagine web standard possono utilizzare l'oggetto XMLHttpRequest per inviare e ricevere dati da ma sono limitati dallo stesso criterio di origine. Gli script di contenuti avviano le richieste per conto dell'origine web in cui è stato inserito lo script dei contenuti e, di conseguenza, i contenuti sono inoltre soggetti alle stesse norme di origine. (Gli script dei contenuti sono stati soggetti alle norme CORB da Chrome 73 e CORS dalla versione 83 di Chrome.) Le origini delle estensioni non sono così limitate - uno script l'esecuzione nella pagina di sfondo o nella scheda in primo piano di un'estensione può comunicare con i server remoti al di fuori a condizione che l'estensione richieda autorizzazioni multiorigine.

Origine estensione

Ogni estensione in esecuzione esiste all'interno della propria origine di sicurezza separata. Senza richiedere ulteriori l'estensione può usare XMLHttpRequest per ottenere risorse all'interno della sua installazione. Per Ad esempio, se un'estensione contiene un file di configurazione JSON denominato config.json, in un config_resources, l'estensione può recuperare il contenuto 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 da se stessa, di' https://www.google.com, il browser lo consente a meno che l'estensione non abbia richiesto il supporto multiorigine appropriato autorizzazioni aggiuntive.

Richiesta di autorizzazioni multiorigine

Aggiungendo host o pattern di corrispondenza degli host (o entrambi) alla sezione delle autorizzazioni delle 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 pattern di corrispondenza, come i seguenti:

  • "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, sono simili ai pattern di corrispondenza degli script di contenuti, ma qualsiasi informazione sul percorso che segue viene ignorato.

Tieni inoltre presente che l'accesso viene concesso sia dall'host che dallo schema. Se un'estensione vuole sia sicura per l'accesso HTTP non sicuro a un determinato host o insieme di host, è necessario 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 pagina in background deve fare attenzione a non cadere vittima del cross-site scripting. In particolare, evita di utilizzare API pericolose come sotto:

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

Limitazione dell'accesso agli script di contenuti alle richieste multiorigine

Quando esegui richieste multiorigine per conto di uno script di contenuti, presta attenzione a evitare pagine web dannose che potrebbero tentare di rubare l'identità di uno script di contenuti. In particolare, non consentire script di contenuti per richiedere un URL arbitrario.

Considera un esempio in cui un'estensione esegue una richiesta multiorigine per consentire a uno script di contenuti scopri il prezzo di un articolo. Un approccio (non sicuro) consiste nel fare in modo 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 descritto in precedenza, lo script dei contenuti può chiedere all'estensione di recuperare qualsiasi URL che l'estensione a cui ha accesso. Una pagina web dannosa potrebbe essere in grado di falsificare questi messaggi e indurre con l’inganno l’estensione a che concede l'accesso alle risorse multiorigine.

Progetta invece gestori di messaggi che limitano le risorse che possono essere recuperate. In basso, solo itemId viene fornito dallo script dei contenuti e non dall'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 l'estensione viene utilizzata su una ostile, un utente malintenzionato di rete (noto anche come "man-in-the-middle") potrebbe modificare la risposta e, potenzialmente, attaccare la tua estensione. Preferisci invece utilizzare 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 elemento content_security_policy al tuo manifest, dovrai assicurarti che tutti gli host a cui che vuoi collegare sono consentiti. Anche se il criterio predefinito non limita le connessioni agli host, fai attenzione quando aggiungi esplicitamente le istruzioni connect-src o default-src.