Zwykłe strony internetowe mogą używać interfejsów API fetch()
lub XMLHttpRequest
do wysyłania i odbierania danych z odległych serwerów, ale są ograniczone przez zasady dotyczące tego samego źródła. Skrypty treści inicjują żądania w imieniu domeny internetowej, do której został wstrzyknięty skrypt treści, dlatego podlegają one również zasadzie tej samej domeny. Źródła rozszerzeń nie są tak ograniczone. Skrypt działający w workerze rozszerzenia lub na karcie na pierwszym planie może łączyć się z serwerami zewnętrznymi poza swoim źródłem, o ile rozszerzenie poprosi o uprawnienia dotyczące wielu źródeł.
Źródło rozszerzenia
Każde uruchomione rozszerzenie ma własny, oddzielny element zabezpieczeń. Bez konieczności żądania dodatkowych uprawnień rozszerzenie może wywoływać fetch()
, aby pobierać zasoby z instalacji. Jeśli na przykład rozszerzenie zawiera w folderze config_resources/
plik konfiguracji JSON o nazwie config.json
, może pobrać jego zawartość w ten sposób:
const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();
Jeśli rozszerzenie próbuje użyć innego źródła zabezpieczeń, na przykład https://www.google.com, przeglądarka zablokuje dostęp do danych, chyba że zażąda ono odpowiednich uprawnień z innych domen.
Prośba o uprawnienia z innych domen
Aby poprosić o dostęp do serwerów zdalnych poza źródłem rozszerzenia, dodaj hosty, wzorce dopasowania lub jedno i drugie do sekcji host_permissions w pliku manifest.
{
"name": "My extension",
...
"host_permissions": [
"https://www.google.com/"
],
...
}
Wartości uprawnień między domenami mogą być pełnymi nazwami hostów, takimi jak:
- "https://www.google.com/"
- "https://www.gmail.com/"
Mogą to być również wzorce dopasowania, na przykład:
- "https://*.google.com/"
- "https://*/"
Wzorzec dopasowywania „https://*” zezwala na dostęp HTTPS do wszystkich dostępnych domen. Pamiętaj, że wzorce dopasowania są podobne do wzorców dopasowania skryptu treści, ale wszelkie informacje o ścieżce po hostie są ignorowane.
Pamiętaj też, że dostęp jest przyznawany zarówno przez hosta, jak i schemat. Jeśli rozszerzenie chce mieć dostęp HTTP (zabezpieczony i niezabezpieczony) do określonego hosta lub zestawu hostów, musi zadeklarować te uprawnienia osobno:
"host_permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
Funkcja Fetch() a XMLHttpRequest()
fetch()
został stworzony specjalnie na potrzeby usług działających w tle i jest zgodny z szerszym trendem w internecie polegającym na odchodzeniu od operacji synchronicznych. Interfejs API XMLHttpRequest()
jest obsługiwany w rozszerzeniach spoza skryptu service worker. Jego wywołanie powoduje uruchomienie modułu obsługi pobierania skryptu service worker rozszerzenia. Nowe treści powinny być tworzone w miarę możliwości w języku fetch()
.
Zagadnienia związane z bezpieczeństwem
Unikanie luk w zabezpieczeniach związanych z atakami typu XSS
Korzystając z zasobów pobieranych za pomocą fetch()
, należy uważać, aby nie stać się ofiarą skryptowania na wielu stronach. Unikaj zwłaszcza 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 wybieraj bezpieczniejsze interfejsy 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;
Ogranicz dostęp do skryptów treści do żądań z innych domen
Podczas wykonywania żądań z innych domen w imieniu skryptu treści pamiętaj, 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 wysyła żądanie między domenami, aby umożliwić skryptowi treści uzyskanie informacji o cenie produktu. Jednym z mniej bezpiecznych rozwiązań jest określenie w skrypcie treści dokładnego zasobu, który ma być pobierany przez stronę 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 przypadku tego podejścia skrypt treści może poprosić rozszerzenie o pobranie dowolnego adresu URL, do którego ma ono dostęp. Złośliwa strona internetowa może być w stanie sfałszować takie wiadomości i nakłonić rozszerzenie do dostępu do zasobów z innych domen.
Zamiast tego zaprojektuj moduły obsługi wiadomości, które ograniczają zasoby, które można pobrać. Poniżej skrypt treści udostępnia tylko parametr 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 => ...
);
preferować HTTPS przed HTTP;
Dodatkowo należy zachować szczególną ostrożność w przypadku zasobów pobieranych przez HTTP. Jeśli Twoje rozszerzenie jest używane w nieprzyjaznej sieci, osoba przeprowadzająca atak sieciowy (czyli "man-in-the-middle") może zmodyfikować odpowiedź i potencjalnie zaatakować Twoje rozszerzenie. Zamiast tego, gdy tylko to możliwe, używaj protokołu HTTPS.
Dostosowywanie polityki bezpieczeństwa treści
Jeśli zmodyfikujesz domyślne zabezpieczenia treści w rozszerzeniu, dodając do pliku manifestu atrybut content_security_policy
, musisz się upewnić, że wszystkie hosty, z którymi chcesz się połączyć, są dozwolone. Chociaż domyślna zasada nie ogranicza połączeń z hostami, należy zachować ostrożność podczas dodawania dyrektyw connect-src
lub default-src
.