XMLHttpRequest z innych domen

Zwykłe strony internetowe mogą używać obiektu XMLHttpRequest do wysyłania i odbierania danych ze zdalnych danych ale ograniczają je te same zasady dotyczące pochodzenia. Skrypty treści inicjują żądania. w imieniu źródła stron internetowych, w którym został wstrzyknięty skrypt, a tym samym treść skrypty są również objęte tą samą zasadą dotyczącą źródła. (Skrypty treści podlegają zasadom CORB od wersji Chrome 73 i CORS od Chrome 83). Źródła rozszerzeń nie są tak ograniczone – skrypt uruchomione na stronie w tle lub na karcie na pierwszym planie mogą komunikować się z serwerami zdalnymi poza swojego źródła, o ile rozszerzenie żąda uprawnień z innych domen.

Pochodzenie rozszerzenia

Każde uruchomione rozszerzenie istnieje w ramach własnego, oddzielnego źródła zabezpieczeń. Bez wysyłania prośby o dodatkowe uprawnienia, rozszerzenie może używać XMLHttpRequest do pobierania zasobów w trakcie instalacji. Dla: Jeśli na przykład rozszerzenie zawiera plik konfiguracji JSON o nazwie config.json, w tagu config_resources, to rozszerzenie może pobrać zawartość pliku w następujący sposób:

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = handleStateChange; // Implemented elsewhere.
xhr.open("GET", chrome.extension.getURL('/config_resources/config.json'), true);
xhr.send();

Jeśli rozszerzenie próbuje użyć innego źródła zabezpieczeń, na przykład https://www.google.com, przeglądarka nie zezwala na to rozszerzenie, chyba że rozszerzenie zażąda odpowiedniego dostępu z innej domeny uprawnień.

Żądanie uprawnień z innych domen

Dodając hosty lub wzorce dopasowania hosta (lub oba te elementy) w sekcji permissions sekcji manifest, rozszerzenie może prosić o dostęp do serwerów zdalnych spoza źródła.

{
  "name": "My extension",
  ...
  "permissions": [
    "https://www.google.com/"
  ],
  ...
}

Wartości uprawnień z innych domen mogą być w pełni kwalifikowanymi nazwami hostów, na przykład:

  • "https://www.google.com/"
  • "https://www.gmail.com/"

Mogą to być również wzorce dopasowania, na przykład:

  • "https://*.google.com/"
  • "https://*/"

Wzorzec dopasowania „https://*/” zezwala na dostęp HTTPS do wszystkich osiągalnych domen. Pamiętaj, że tutaj są podobne do wzorców dopasowania skryptu treści, ale wszystkie informacje o ścieżce po jest ignorowany.

Pamiętaj też, że dostęp jest przyznawany zarówno przez hosta, jak i według schematu. Jeśli rozszerzenie potrzebuje zarówno bezpiecznych, niezabezpieczony dostęp HTTP do danego hosta lub zestawu hostów, uprawnienia należy zadeklarować oddzielnie:

"permissions": [
  "http://www.google.com/",
  "https://www.google.com/"
]

Bezpieczeństwo

Unikanie luk w zabezpieczeniach (cross-site scripting)

Podczas korzystania z zasobów pobranych za pomocą XMLHttpRequest strona w tle powinna uważać, aby nie padają ofiarą ataków typu cross-site scripting. W szczególności należy unikać używania niebezpiecznych interfejsów API, takich jak poniżej:

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be evaluating an evil script!
    var resp = eval("(" + xhr.responseText + ")");
    ...
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be injecting a malicious script!
    document.getElementById("resp").innerHTML = xhr.responseText;
    ...
  }
}
xhr.send();

Zamiast tego korzystaj z bezpieczniejszych interfejsów API, które nie uruchamiają skryptów:

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // JSON.parse does not evaluate the attacker's scripts.
    var resp = JSON.parse(xhr.responseText);
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // innerText does not let the attacker inject HTML elements.
    document.getElementById("resp").innerText = xhr.responseText;
  }
}
xhr.send();

Ograniczanie dostępu do skryptu treści w przypadku żądań z innych domen

Podczas wykonywania żądań z innych domen w imieniu skryptu treści zwróć uwagę na ochronę przed złośliwe strony internetowe, które mogą próbować podszywać się pod skrypt treści. W szczególności nie zezwalaj na: skryptów treści, które wysyłają żądania dowolnych adresów URL.

Zobacz przykład, w którym rozszerzenie wysyła żądanie z innej domeny, aby zezwolić na skrypt treści poznać cenę danego produktu. Jedną z metod (niezabezpieczonych) jest określenie w skrypcie treści 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()));

Przy zastosowaniu powyższego rozwiązania skrypt treści może zażądać od rozszerzenia pobrania dowolnego adresu URL, którego rozszerzenie ma dostęp do. Złośliwa strona może być w stanie sfałszować takie wiadomości i skłonić rozszerzenie do przyznawanie dostępu do zasobów z innych domen.

Zamiast tego zaprojektuj moduły obsługi komunikatów, które ograniczają zasoby, które można pobrać. Poniżej tylko Pole itemId jest dostarczane przez skrypt treści, a nie pełny adres URL.

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
      if (request.contentScriptQuery == 'queryPrice') {
        var 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 => ...);

Preferowany protokół HTTPS przed HTTP

Zachowaj szczególną ostrożność także w przypadku zasobów pobieranych przez HTTP. Jeśli Twoje rozszerzenie jest używane na wroga sieć, haker sieci (inaczej "man-in-the-middle") może zmodyfikować odpowiedź i potencjalnie zaatakować rozszerzenie. W miarę możliwości wybieraj HTTPS.

Dostosowywanie polityki bezpieczeństwa treści

Jeśli zmienisz domyślną Content Security Policy dla aplikacji lub rozszerzeń, dodając content_security_policy do pliku manifestu, musisz upewnić się, że wszystkie hosty, dla których które chcesz połączyć. Domyślna zasada nie ogranicza połączeń do hostów, zachowaj ostrożność podczas dodawania dyrektywy connect-src lub default-src.