Le pagine web normali possono utilizzare le API fetch() o XMLHttpRequest per inviare e ricevere dati da server remoti, ma sono limitate dalla policy della stessa origine. Gli script di contenuti avviano le richieste
per conto dell'origine web in cui sono stati inseriti e, pertanto, gli script di contenuti
sono soggetti anche alla policy della stessa origine. Le origini delle estensioni non sono così limitate. Uno script
in esecuzione in un service worker dell'estensione o in una scheda in primo piano può comunicare con server remoti al di fuori della
sua origine, a condizione che l'estensione richieda le autorizzazioni host.
Origine dell'estensione
Ogni estensione in esecuzione esiste all'interno della propria origine di sicurezza separata. Senza richiedere privilegi aggiuntivi, l'estensione può chiamare fetch() 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 nel seguente modo:
const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();
Se l'estensione tenta di richiedere contenuti da un'origine di sicurezza diversa dalla propria, ad esempio https://www.google.com, questa verrà trattata come una richiesta multiorigine, a meno che l'estensione non disponga delle autorizzazioni host. Le richieste cross-origin vengono sempre trattate come tali negli script di contenuti, anche se l'estensione dispone delle autorizzazioni host.
Richiedere le autorizzazioni cross-origin
Per richiedere l'accesso a server remoti al di fuori dell'origine di un'estensione, aggiungi host, pattern di corrispondenza, o entrambi alla sezione host_permissions del file manifest.
{
"name": "My extension",
...
"host_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 tutte le informazioni sul percorso che seguono l'host vengono ignorate.
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:
"host_permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
Fetch() rispetto a XMLHttpRequest()
fetch() è stato creato appositamente per i service worker e segue una tendenza web più ampia che si allontana dalle operazioni sincrone. L'API XMLHttpRequest() è supportata nelle estensioni al di fuori del service worker e la sua chiamata attiva il gestore di recupero del service worker dell'estensione. I nuovi lavori dovrebbero favorire fetch() ove possibile.
Considerazioni sulla sicurezza
Evitare le vulnerabilità di cross-site scripting
Quando utilizzi le risorse recuperate tramite fetch(), il documento fuori schermo, il riquadro laterale o il popup devono fare attenzione a non
essere vittime di cross-site scripting. In particolare, evita di utilizzare API pericolose come innerHTML. Ad esempio:
const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = jsonData;
...
In alternativa, preferisci API più sicure che non eseguono script:
const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// JSON.parse does not evaluate the attacker's scripts.
let resp = JSON.parse(jsonData);
const response = await fetch("https://api.example.com/data.json");
const jsonData = response.json();
// textContent does not let the attacker inject HTML elements.
document.getElementById("resp").textContent = jsonData;
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 molto sicuro sarebbe quello di far specificare allo script di contenuti 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, lo script di contenuti fornisce solo itemId e non l'URL completo.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.contentScriptQuery == 'queryPrice') {
const 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 aggressore di rete (noto anche come "man-in-the-middle") potrebbe modificare la risposta e, potenzialmente, attaccare la tua estensione. In alternativa, preferisci HTTPS ove possibile.
Modificare la policy di sicurezza dei contenuti
Se modifichi la policy di sicurezza dei contenuti predefinita per la tua estensione aggiungendo un
content_security_policy attributo al file manifest, dovrai assicurarti che tutti gli host a cui
vuoi connetterti siano consentiti. Sebbene la policy predefinita non limiti le connessioni agli host, fai attenzione quando aggiungi esplicitamente le direttive connect-src o default-src.