Zwykłe strony internetowe mogą używać interfejsów API fetch()
lub XMLHttpRequest
do wysyłania i odbierania danych z serwerów zdalnych, ale ograniczają je te same zasady dotyczące źródła. Skrypty treści inicjują żądania w imieniu źródła stron internetowych, do którego wstrzyknięto skrypt treści. Dlatego też skrypty treści również podlegają tej samej zasadzie dotyczącej źródła. Źródło rozszerzeń nie jest tak ograniczone. Skrypt uruchamiany w skrypcie usługi rozszerzeń lub na karcie na pierwszym planie może komunikować się z serwerami zdalnymi spoza swojego źródła, o ile rozszerzenie żąda uprawnień z innych domen.
Źródło rozszerzenia
Każde aktywne rozszerzenie istnieje w osobnym źródle zabezpieczeń. Bez wysyłania żądania dodatkowych uprawnień rozszerzenie może wywołać metodę fetch()
, aby pobrać zasoby w ramach instalacji. Jeśli na przykład rozszerzenie zawiera plik konfiguracji JSON o nazwie config.json
, w folderze config_resources/
może pobrać zawartość tego pliku 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ń, np. https://www.google.com, przeglądarka go blokuje, chyba że żąda odpowiednich uprawnień z innych domen.
Wysyłanie prośby o uprawnienia z innych domen
Aby poprosić o dostęp do serwerów zdalnych spoza źródła rozszerzenia, dodaj hosty lub wzorce dopasowania do sekcji host_permissions w pliku manifestu.
{
"name": "My extension",
...
"host_permissions": [
"https://www.google.com/"
],
...
}
Wartości uprawnień z innych domen mogą być w pełni kwalifikowanymi nazwami hostów, takimi jak:
- "https://www.google.com/"
- "https://www.gmail.com/"
Mogą też być wzorcami dopasowania, takimi jak:
- "https://*.google.com/"
- "https://*/"
Wzorzec dopasowania „https://*/” umożliwia dostęp HTTPS do wszystkich osiągalnych domen. Pamiętaj, że w tym przypadku wzorce dopasowania są podobne do wzorców dopasowania skryptów treści, ale informacje o ścieżce występujące po hoście są ignorowane.
Pamiętaj też, że dostęp jest przyznawany zarówno według hosta, jak i schematu. Jeśli rozszerzenie chce mieć dostęp zarówno do bezpiecznego, jak i niebezpiecznego protokołu HTTP do określonego hosta lub zestawu hostów, musi zadeklarować te uprawnienia oddzielnie:
"host_permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
Fetch() a XMLHttpRequest()
Funkcja fetch()
została stworzona specjalnie z myślą o skryptach service worker, która jest zgodna z szerszym trendem korzystania z internetu zamiast operacji synchronicznych. Interfejs API XMLHttpRequest()
jest obsługiwany w rozszerzeniach poza skryptem service worker, a wywołanie go powoduje uruchomienie modułu obsługi pobierania tego skryptu. W miarę możliwości należy tworzyć nowe materiały w zamian za fetch()
.
Kwestie bezpieczeństwa
Unikanie luk w zabezpieczeniach między witrynami
Podczas korzystania z zasobów pobranych za pomocą fetch()
uważaj, aby nie dać się zaskoczyć skrypcie między witrynami w dokumencie, panelu bocznym lub wyskakującym okienku. W szczególności unikaj korzystania z 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 wybierz 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 skryptu treści do żądań z innych domen
Podczas wykonywania żądań z innych domen w imieniu skryptu dotyczącego treści uważaj na szkodliwe strony internetowe, które mogą próbować podszywać się pod skrypt treści. W szczególności nie zezwalaj skryptom treści na wysyłanie żądań dowolnych adresów URL.
Przeanalizujmy przykład, w którym rozszerzenie wysyła żądanie z innej domeny, aby umożliwić skryptowi treści sprawdzenie ceny produktu. Niepewne podejście polega na tym, że skrypt treści określa dokładny zasób, 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 takiej sytuacji 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 sfałszować takie komunikaty i skłonić rozszerzenie do przyznania dostępu do zasobów z innych domen.
Zamiast tego zaprojektuj moduły obsługi wiadomości, które ograniczają liczbę zasobów, które można pobierać. Poniżej skrypt treści zawiera 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 => ...
);
Preferuj HTTPS zamiast HTTP
Zachowaj też 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 (inaczej "man-in-the-middle") może zmodyfikować odpowiedź, a potem zaatakować rozszerzenie. W miarę możliwości stosuj protokół HTTPS.
Dostosowywanie zasady bezpieczeństwa treści
Jeśli zmodyfikujesz domyślną zasadę Content Security Policy dla rozszerzenia, dodając do pliku manifestu atrybut content_security_policy
, musisz się upewnić, że wszystkie hosty, z którymi chcesz się połączyć, są dozwolone. Zasada domyślna nie ogranicza połączeń z hostami, ale zachowaj ostrożność podczas dodawania dyrektyw connect-src
lub default-src
.