XMLHttpRequest z innych domen

Zwykłe strony internetowe mogą używać obiektu XMLHttpRequest do wysyłania i odbierania danych z serwerów zdalnych, ale są ograniczone przez zasady dotyczące tego samego pochodzenia. Skrypty treści inicjują żądania w imieniu origin strony internetowej, do którego zostały wstrzyknięte. Dlatego też skrypty treści podlegają polityce tego samego pochodzenia. (Skrypty treści podlegają CORB od Chrome 73 i CORS od Chrome 83). Pochodzenie rozszerzeń nie jest tak ograniczone – skrypt działający na stronie w tle lub na karcie na pierwszym planie rozszerzenia może komunikować się z serwerami zdalnymi spoza swojego pochodzenia, o ile rozszerzenie zażąda uprawnień do komunikacji między domenami.

Źródło rozszerzenia

Każde działające rozszerzenie ma własne źródło zabezpieczeń. Bez żądania dodatkowych uprawnień rozszerzenie może używać XMLHttpRequest do pobierania zasobów w ramach 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:

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ń niż własne, np. https://www.google.com, przeglądarka na to nie zezwoli, chyba że rozszerzenie poprosiło o odpowiednie uprawnienia do korzystania z innych domen.

Wysyłanie prośby o uprawnienia do korzystania z innych domen

Dodając hosty lub wzorce dopasowania hostów (albo jedno i drugie) do sekcji permissions w pliku manifestu, rozszerzenie może poprosić o dostęp do serwerów zdalnych spoza jego pochodzenia.

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

Wartości uprawnień do zasobów z innych domen mogą być pełnymi i jednoznacznymi nazwami hostów, np.:

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

Mogą to być też wzorce dopasowania, takie jak te:

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

Wzorzec dopasowania „https://*/” umożliwia dostęp HTTPS do wszystkich osiągalnych domen. Należy pamiętać, że wzorce dopasowania są 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 według hosta, jak i według schematu. Jeśli rozszerzenie chce mieć dostęp do danego hosta lub zestawu hostów zarówno przez bezpieczny, jak i niezabezpieczony protokół HTTP, musi zadeklarować uprawnienia oddzielnie:

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

Bezpieczeństwo

Unikanie luk w zabezpieczeniach typu cross-site scripting

Podczas korzystania z zasobów pobranych za pomocą XMLHttpRequest strona w tle powinna zachować ostrożność, aby nie paść ofiarą ataku typu cross-site scripting. W szczególności unikaj używania niebezpiecznych interfejsów API, takich jak te 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 używaj 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 skryptów treści do żądań z innych domen

Podczas wykonywania żądań z różnych źródeł w imieniu skryptu treści zachowaj 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ń dotyczących dowolnych adresów URL.

Rozważmy przykład, w którym rozszerzenie wysyła żądanie do innej domeny, aby skrypt treści mógł sprawdzić cenę produktu. Jednym z (niezabezpieczonych) podejść byłoby określenie przez skrypt treści dokładnego zasobu, który ma zostać pobrany 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 powyższym podejściu 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 z innych domen.

Zamiast tego zaprojektuj procedury obsługi wiadomości, które ograniczają zasoby, jakie można pobrać. Poniżej skrypt treści podaje tylko itemId, a nie cały 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 => ...);

Pierwszeństwo protokołu HTTPS przed HTTP

Zachowaj szczególną ostrożność w przypadku zasobów pobieranych przez HTTP. Jeśli rozszerzenie jest używane w niebezpiecznej sieci, osoba przeprowadzająca atak w sieci (czyli "man-in-the-middle") może zmodyfikować odpowiedź i potencjalnie zaatakować rozszerzenie. Zamiast tego, jeśli to możliwe, używaj protokołu HTTPS.

Dostosowywanie standardu Content Security Policy

Jeśli zmodyfikujesz domyślne Content Security Policy dla aplikacji lub rozszerzeń, dodając atrybut content_security_policy do pliku manifestu, musisz się upewnić, że wszystkie hosty, z którymi chcesz się połączyć, są dozwolone. Chociaż zasady domyślne nie ograniczają połączeń z hostami, zachowaj ostrożność, gdy dodajesz dyrektywy connect-src lub default-src.