Zwykłe strony internetowe mogą używać interfejsów API fetch() lub XMLHttpRequest do wysyłania i odbierania danych z serwerów zdalnych, ale obowiązuje je zasada dotycząca tego samego pochodzenia. Skrypty treści inicjują żądania w imieniu origin strony, do którego zostały wstrzyknięte, dlatego też podlegają tej samej zasadzie dotyczącej tego samego pochodzenia.
podlegają również zasadzie tego samego pochodzenia. Pochodzenia rozszerzeń nie są tak ograniczone. Skrypt service worker wykonywany w usłudze Service Worker rozszerzenia lub na karcie na pierwszym planie może komunikować się z serwerami zdalnymi spoza swojego pochodzenia, o ile rozszerzenie poprosi o uprawnienia do hosta.
Pochodzenie rozszerzenia
Każde działające rozszerzenie istnieje w swoim własnym, oddzielnym pochodzeniu zabezpieczeń. Bez proszenia o dodatkowe uprawnienia rozszerzenie może wywołać fetch(), aby pobrać zasoby w ramach swojej instalacji. Jeśli na przykład rozszerzenie zawiera plik konfiguracyjny JSON o nazwie config.json w folderze config_resources/, może pobrać zawartość pliku w ten sposób:
const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();
Jeśli rozszerzenie próbuje zażądać treści z pochodzenia zabezpieczeń innego niż własne, np. https://www.google.com, zostanie to potraktowane jako żądanie międzyźródłowe, chyba że rozszerzenie ma uprawnienia do hosta. Żądania międzyźródłowe są zawsze traktowane jako takie w skryptach treści, nawet jeśli rozszerzenie ma uprawnienia do hosta.
Żądanie uprawnień międzyźródłowych
Aby poprosić o dostęp do serwerów zdalnych spoza pochodzenia rozszerzenia, dodaj hosty, wzorce dopasowania, lub oba te elementy do sekcji host_permissions w pliku manifestu.
{
"name": "My extension",
...
"host_permissions": [
"https://www.google.com/"
],
...
}
Wartości uprawnień międzyźródłowych mogą być pełnymi i jednoznacznymi nazwami hostów, takimi jak te:
- "https://www.google.com/"
- "https://www.gmail.com/"
Mogą to też być wzorce dopasowania, takie jak te:
- "https://*.google.com/"
- "https://*/"
Wzorzec dopasowania „https://*/” umożliwia dostęp HTTPS do wszystkich osiągalnych domen. Pamiętaj, że wzorce dopasowania są tu podobne do wzorców dopasowania skryptów treści, ale wszelkie informacje o ścieżce po hoście są ignorowane.
Pamiętaj też, że dostęp jest przyznawany zarówno przez hosta, jak i przez schemat. Jeśli rozszerzenie chce mieć dostęp HTTP zarówno bezpieczny, jak i niezabezpieczony do danego hosta lub zestawu hostów, musi zadeklarować uprawnienia oddzielnie:
"host_permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
Fetch() a XMLHttpRequest()
Funkcja fetch() została utworzona specjalnie dla skryptów Service Worker i jest zgodna z szerszym trendem w internecie, który odchodzi od operacji synchronicznych. Interfejs API XMLHttpRequest() jest obsługiwany w rozszerzeniach poza skryptem Service Worker, a jego wywołanie powoduje uruchomienie procedury obsługi pobierania skryptu Service Worker rozszerzenia. W nowych projektach należy w miarę możliwości używać funkcji fetch().
Względy bezpieczeństwa
Unikanie luk w zabezpieczeniach umożliwiających wykonywanie skryptów między witrynami
Podczas korzystania z zasobów pobranych za pomocą funkcji fetch() dokument poza ekranem, panel boczny lub wyskakujące okienko powinny uważać, aby nie paść ofiarą ataku typu cross-site scripting. W szczególności unikaj używania niebezpiecznych interfejsów API, takich jak innerHTML. Na przykład:
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;
...
Zamiast tego używaj bezpieczniejszych interfejsów API, które nie uruchamiają skryptów:
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;
Ograniczanie dostępu skryptu treści do żądań międzyźródłowych
Podczas wykonywania żądań międzyźródłowych w imieniu skryptu treści należy zachować ostrożność, aby chronić się przed złośliwymi stronami internetowymi, które mogą próbować podszywać się pod skrypt treści. W szczególności nie zezwalaj skryptom treści na wysyłanie żądań do dowolnego adresu URL.
Rozważmy przykład, w którym rozszerzenie wykonuje żądanie międzyźródłowe, aby skrypt treści mógł poznać cenę produktu. Jednym z niezbyt bezpiecznych rozwiązań byłoby umożliwienie skryptowi treści określenie dokładnego zasobu, który ma zostać pobrany przez stronę działającą w tle.
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())
);
W powyższym rozwiązaniu skrypt treści może poprosić rozszerzenie o pobranie dowolnego adresu URL, do którego rozszerzenie ma dostęp. Złośliwa strona internetowa może sfałszować takie wiadomości i nakłonić rozszerzenie do przyznania dostępu do zasobów międzyźródłowych.
Zamiast tego zaprojektuj procedury obsługi wiadomości, które ograniczają zasoby, które można pobrać. Poniżej skrypt treści podaje tylko itemId, a nie pełny adres URL.
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 => ...
);
Preferowanie protokołu HTTPS zamiast HTTP
Dodatkowo zachowaj szczególną ostrożność w przypadku zasobów pobieranych przez HTTP. Jeśli rozszerzenie jest używane w wrogiej sieci, atakujący sieć (tzw. "man-in-the-middle") może zmodyfikować odpowiedź i potencjalnie zaatakować rozszerzenie. Zamiast tego w miarę możliwości używaj protokołu HTTPS.
Dostosowywanie standardu Content Security Policy
Jeśli zmodyfikujesz domyślny Content Security Policy dla rozszerzenia, dodając atrybut
content_security_policy do manifestu, musisz się upewnić, że wszystkie hosty, z którymi
chcesz się połączyć, są dozwolone. Domyślna zasada nie ogranicza połączeń z hostami, ale zachowaj ostrożność podczas dodawania dyrektyw connect-src lub default-src.