Le pagine web normali possono utilizzare le API fetch()
o XMLHttpRequest
per inviare e ricevere dati da server remoti, ma sono limitate dai criteri della stessa origine. Gli script di contenuti avviano richieste per conto dell'origine web in cui sono stati inseriti e, pertanto, sono soggetti anche alle norme relative alla stessa origine. Le origini delle estensioni non sono così limitate. Uno script
in esecuzione in un service worker di estensione o in una scheda in primo piano può comunicare con server remoti al di fuori
della propria 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ò 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 utilizzare un'origine di sicurezza diversa da se stessa, ad esempio https://www.google.com, il browser non lo consente, a meno che l'estensione non abbia richiesto le autorizzazioni di accesso tra origini appropriate.
Richiedere autorizzazioni cross-origin
Per richiedere l'accesso a server remoti esterni all'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 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 questi:
- "https://*.google.com/"
- "https://*/"
Un pattern di corrispondenza "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 di contenuti, ma qualsiasi informazione sul percorso che segue l'host viene ignorata.
Tieni inoltre presente che l'accesso viene concesso sia per l'host che per lo schema. Se un'estensione desidera 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() e XMLHttpRequest()
fetch()
è stato creato appositamente per i worker di servizio e segue una tendenza più ampia del web 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 devono favorire fetch()
, ove possibile.
Considerazioni sulla sicurezza
Evitare le vulnerabilità di cross-site scripting
Quando utilizzi le risorse recuperate tramite fetch()
, il documento, il riquadro laterale o il popup offscreen deve fare attenzione a non diventare vittima di cross-site scripting. In particolare, evita di utilizzare API pericolose comeinnerHTML
. 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;
...
Preferisci invece 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 rubare l'identità di 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 rilevare il prezzo di un elemento. Un approccio non molto sicuro consiste nel fare in modo che lo script dei 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 precedente, lo script di 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 l'estensione a dare accesso a risorse cross-origin.
Progetta invece gestori dei messaggi che limitano le risorse che possono essere recuperate. Di seguito, lo script dei contenuti fornisce soloitemId
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 malintenzionato (noto anche come "man-in-the-middle") potrebbe modificare la risposta e, potenzialmente, attaccare la tua estensione. Preferisci invece HTTPS, se possibile.
Modificare i criteri di sicurezza dei contenuti
Se modifichi il Criterio di sicurezza del contenuto predefinito per l'estensione aggiungendo un attributo content_security_policy
al manifest, devi 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
.