Izolacja witryn dla programistów stron internetowych

W Chrome 67 na komputery domyślnie włączona jest nowa funkcja izolacja witryn. Z tego artykułu dowiesz się, czym jest izolacja witryny, dlaczego jest ona potrzebna i dlaczego deweloperzy powinni o niej wiedzieć.

Czym jest izolacja witryn?

Internet służy do oglądania filmów z kotami i zarządzania portfelami kryptowalutowymi, ale nie chcesz, aby fluffycats.example miał dostęp do Twoich cennych kryptowalut. Na szczęście dzięki polityce dotyczącym witryn o tym samym pochodzeniu witryny zwykle nie mają dostępu do danych innych witryn w przeglądarce. Mimo to złośliwe witryny mogą próbować obejść te zasady, aby atakować inne witryny. Czasami w kodzie przeglądarki, który egzekwuje zasadę takiego samego pochodzenia, występują błędy zabezpieczeń. Zespół Chrome stara się jak najszybciej naprawiać takie błędy.

Izolacja witryn to funkcja bezpieczeństwa w Chrome, która stanowi dodatkową linię obrony przed atakami. Dzięki temu strony z różnych witryn są zawsze umieszczane w różnych procesach, z których każdy działa w piaskownicy, która ogranicza jego możliwości. Zabezpiecza to również przed odbieraniem przez proces określonych typów danych poufnych z innych witryn. Dzięki funkcji izolacji witryn złośliwe witryny mają znacznie mniejsze możliwości korzystania z atakowania przez spekulacyjne kanały boczne, takich jak Spectre, aby kraść dane z innych witryn. Gdy zespół Chrome zakończy wdrażanie dodatkowych środków, izolacja witryn będzie pomocna nawet wtedy, gdy strona atakującego naruszy niektóre zasady w ramach własnego procesu.

Izolacja witryn skutecznie utrudnia niesprawdzonym witrynom dostęp do informacji z Twoich kont w innych witrynach lub kradzież tych informacji. Zapewnia ona dodatkową ochronę przed różnymi typami błędów związanych z bezpieczeństwem, takimi jak niedawne ataki kanałem Meltdown i Spectre.

Więcej informacji o izolacji witryn znajdziesz w artykule na blogu Google o bezpieczeństwie.

Cross-Origin Read Blocking

Nawet jeśli wszystkie strony międzywitrynowe są umieszczane w osobnych procesach, strony mogą nadal prawidłowo żądać niektórych zasobów podrzędnych międzywitrynowych, takich jak obrazy i kod JavaScript. Złośliwa strona internetowa może używać elementu <img> do wczytywania pliku JSON z danymi wrażliwymi, takimi jak saldo bankowe:

<img src="https://your-bank.example/balance.json" />
<!-- Note: the attacker refused to add an `alt` attribute, for extra evil points. -->

Bez izolacji witryny zawartość pliku JSON trafia do pamięci procesu renderowania, gdzie renderowanie zauważa, że nie jest to prawidłowy format obrazu, i nie renderuje obrazu. Jednak atakujący może wykorzystać lukę w zabezpieczeniach, taką jak Spectre, aby odczytać ten fragment pamięci.

Zamiast <img>, atakujący może też użyć <script>, aby zapisać dane wrażliwe w pamięci:

<script src="https://your-bank.example/balance.json"></script>

Cross-Origin Read Blokowanie (CORB) to nowa funkcja zabezpieczeń, która zapobiega przedostaniu się treści z balance.json do pamięci procesu renderowania ze względu na typ MIME.

Omówmy, jak działa CORB. Witryna może prosić serwer o 2 rodzaje zasobów:

  1. zasoby danych, takie jak dokumenty HTML, XML i JSON;
  2. zasobów multimedialnych, takich jak obrazy, JavaScript, CSS czy czcionki;

Witryna może otrzymywać zasoby danych z własnego punktu początkowego lub z innych punktów początkowych z dozwolonymi nagłówkami CORS, takimi jak Access-Control-Allow-Origin: *. Z drugiej strony zasoby multimedialne mogą być uwzględniane z dowolnego źródła, nawet bez rygorystycznych nagłówków CORS.

CORB uniemożliwia procesowi renderowania otrzymywanie zasobów danych z wielu źródeł (np. HTML, XML lub JSON), jeśli:

  • zasób ma nagłówek X-Content-Type-Options: nosniff.
  • CORS nie zezwala wyraźnie na dostęp do zasobu

Jeśli zasób danych między domenami nie ma ustawionego nagłówka X-Content-Type-Options: nosniff, CORB próbuje skanować treść odpowiedzi, aby określić, czy jest to kod HTML, XML czy JSON. Jest to konieczne, ponieważ niektóre serwery internetowe są niepoprawnie skonfigurowane i wyświetlają obrazy jako text/html.

Zasoby danych zablokowane przez zasadę CORB są przedstawiane dla procesu jako puste, mimo że żądanie nadal występuje w tle. W efekcie szkodliwa strona internetowa ma trudności z pobraniem do procesu kradzieży danych z różnych witryn.

Aby zapewnić optymalne bezpieczeństwo i korzystać z zalet CORB, zalecamy wykonanie tych czynności:

  • Oznacz odpowiedzi odpowiednim nagłówkiem Content-Type. (np. zasoby HTML powinny być wyświetlane jako text/html, zasoby JSON z typem MIME JSON i zasoby XML z typem MIME XML).
  • Aby zrezygnować z nagłówka, użyj nagłówka X-Content-Type-Options: nosniff. Bez tego nagłówka Chrome wykonuje szybką analizę treści, aby potwierdzić, że typ jest prawidłowy, ale ponieważ ta metoda pozwala na przepuszczenie odpowiedzi, aby uniknąć blokowania takich rzeczy jak pliki JavaScript, lepiej jest samodzielnie zadbać o właściwe działanie.

Więcej informacji znajdziesz w artykule na temat CORB dla webmasterów lub w szczegółowym wyjaśnieniu CORB.

Dlaczego twórcy stron internetowych powinni dbać o izolację witryn?

Izolacja witryn to w większości funkcja przeglądarki działająca w tle, która nie jest bezpośrednio widoczna dla twórców stron internetowych. Nie musisz na przykład uczyć się nowego interfejsu API dostępnego przez sieć. Ogólnie strony internetowe nie powinny się różnić, gdy są uruchamiane z izolacją witryn lub bez niej.

Od tej reguły są jednak pewne wyjątki. Włączenie izolacji witryny wiąże się z kilkoma subtelnymi skutkami ubocznymi, które mogą mieć wpływ na Twoją witrynę. Prowadzimy listę znanych problemów z izolacją witryn, a najważniejsze z nich opisujemy poniżej.

Układ na całą stronę nie jest już synchroniczny

W przypadku izolacji witryn nie ma już gwarancji, że układ pełnej strony będzie synchroniczny, ponieważ ramki strony mogą być rozmieszczone w kilku procesach. Może to mieć wpływ na strony, jeśli zakładają one, że zmiana układu jest natychmiast rozpowszechniana do wszystkich ramek na stronie.

Przyjrzyjmy się np. witrynie o nazwie fluffykittens.example, która komunikuje się z widżetem społecznościowym hostowanym w social-widget.example:

<!-- https://fluffykittens.example/ -->
<iframe src="https://social-widget.example/" width="123"></iframe>
<script>
  const iframe = document.querySelector('iframe');
  iframe.width = 456;
  iframe.contentWindow.postMessage(
    // The message to send:
    'Meow!',
    // The target origin:
    'https://social-widget.example'
  );
</script>

Na początku szerokość widżetu społecznościowego <iframe> wynosi 123 pikseli. Jednak potem strona FluffyKittens zmienia szerokość na 456 piks. (układ wywołujący) i wysyła wiadomość do widżetu społecznościowego, który zawiera ten kod:

<!-- https://social-widget.example/ -->
<script>
  self.onmessage = () => {
    console.log(document.documentElement.clientWidth);
  };
</script>

Gdy widżet społecznościowy otrzyma wiadomość za pomocą interfejsu API postMessage, rejestruje szerokość jego głównego elementu <html>.

Która wartość szerokości jest rejestrowana? Przed włączeniem izolacji witryn w Chrome odpowiedź brzmiała 456. Uzyskanie dostępu do document.documentElement.clientWidth wymusza układ, który wcześniej był synchroniczny, zanim został włączony w Chrome izolacja witryn. Jednak po włączeniu izolacji witryn przekazywanie widżetów społecznościowych z innych domen odbywa się teraz asynchronicznie w ramach osobnego procesu. Dlatego odpowiedzią może być też 123, czyli stara wartość width.

Jeśli strona zmieni rozmiar zasobu <iframe> z innej domeny, a potem wyśle do niej tag postMessage, to przy izolacji witryny ramka odbierająca może nie rozpoznać nowego rozmiaru w momencie otrzymania wiadomości. Ogólnie rzecz biorąc, może to doprowadzić do uszkodzenia stron, jeśli założą, że zmiana układu od razu dotyczy wszystkich ramek na stronie.

W tym konkretnym przykładzie bardziej niezawodne rozwiązanie polega na ustawieniu wartości width w ramce nadrzędnej i wykrywanie tej zmiany w ramce <iframe> przez nasłuchiwanie zdarzenia resize.

Moduł obsługi wyładowywania może częściej osiągać limit czasu.

Gdy ramka jest przełączana lub zamykana, stary dokument oraz wszystkie zapisane w nim dokumenty podrzędne wywołują unload. Jeśli nowa nawigacja odbywa się w tym samym procesie renderowania (np. w przypadku nawigacji w tej samej domenie), moduły obsługi unload starego dokumentu i jego ramek podrzędnych mogą działać przez dowolny czas, zanim będą mogły zostać zatwierdzone na potrzeby nowej nawigacji.

addEventListener('unload', () => {
  doSomethingThatMightTakeALongTime();
});

W takim przypadku elementy obsługi unload we wszystkich ramkach są bardzo niezawodne.

Jednak nawet bez izolacji witryny niektóre nawigacje w ramce głównej są wieloprocesowe, co wpływa na działanie wyrażenia unload. Jeśli na przykład przejdziesz z old.example do new.example, wpisując adres URL na pasku adresu, przejście do new.example nastąpi w ramach nowego procesu. Po wyświetleniu strony new.example w tle w procesie old.example działają w tle moduły obsługi rozładowania old.example i jego ramek podrzędnych, a po zakończeniu działania strony new.example stare moduły obsługi rozładowania są zamykane, jeśli nie skończą pracy w określonym czasie. Ponieważ moduły obsługi wylogowywania mogą nie zakończyć działania przed upływem limitu czasu, zachowanie podczas wylogowywania jest mniej niezawodne.

Dzięki funkcji izolacji witryn wszystkie przejścia między witrynami stają się przejściami między procesami, dzięki czemu dokumenty z różnych witryn nie korzystają z tego samego procesu. W efekcie ta sytuacja ma zastosowanie w większej liczbie przypadków, a moduły obsługi wyładowywania z <iframe> mają często opisane powyżej działania w tle i czas oczekiwania.

Kolejną różnicą wynikającą z izolacji witryn jest nowe równoległe sortowanie obsługi odciążania: bez izolacji witryn obsługa odciążania jest wykonywana w ścisłej kolejności od góry do dołu w ramach poszczególnych ramek. Jednak dzięki funkcji izolaowania witryn moduły obsługi wyładowania są wykonywane równolegle w różnych procesach.

To podstawowe konsekwencje włączenia izolacji witryn. Zespół Chrome pracuje nad zwiększeniem niezawodności modułów obsługi wyładowania w przypadku typowych zastosowań, w których jest to możliwe. Jesteśmy również świadomi błędów, które powodują, że moduły obsługi wyładowywania ramek podrzędnych nie są jeszcze w stanie korzystać z niektórych funkcji i pracujemy nad ich rozwiązaniem.

Ważnym elementem obsługi wyładowywania jest wysyłanie pingów na koniec sesji. Zwykle odbywa się to w następujący sposób:

addEventListener('pagehide', () => {
  const image = new Image();
  img.src = '/end-of-session';
});

Lepszym i bardziej niezawodnym podejściem w świetle tej zmiany jest użycie funkcji navigator.sendBeacon:

addEventListener('pagehide', () => {
  navigator.sendBeacon('/end-of-session');
});

Jeśli chcesz mieć większą kontrolę nad żądaniem, możesz użyć opcji keepalive interfejsu Fetch API:

addEventListener('pagehide', () => {
  fetch('/end-of-session', {keepalive: true});
});

Podsumowanie

Izolacja witryn utrudnia niezaufanym witrynom dostęp do Twoich kont w innych witrynach lub kradzież informacji z nich, ponieważ odizoluje każdą witrynę osobno. W ramach tego procesu CORB stara się nie przekazywać zasobów danych wrażliwych do procesu renderowania. Nasze rekomendacje pomogą Ci w pełni wykorzystać te nowe funkcje zabezpieczeń.

Dziękuję Alex Moshchuk, Charlie Reis, Jason Miller, Nasko Oskov, Philip Walton, Shubhie Panicker i Thomas Steiner za przeczytanie wersji roboczej tego artykułu i przekazanie swoich opinii.