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 ataków spekulatywnych typu side-channel, takich jak Spectre, do kradzieży danych 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 stwierdza, ż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 Blocking (CORB) to nowa funkcja zabezpieczeń, która na podstawie typu MIME zapobiega temu, aby zawartość adresu balance.json trafiła do pamięci procesu renderowania.

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

  1. zasobów danych, takich jak dokumenty HTML, XML lub 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 zasobów multimedialnych można używać z dowolnego punktu początkowego, nawet bez zezwalających nagłówków CORS.

CORB uniemożliwia procesowi renderowania otrzymywanie zasobów danych z innych ź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 procesowi jako puste, chociaż żądanie nadal jest realizowane w tle. W związku z tym szkodliwa strona internetowa ma trudności z pobieraniem danych z różnych witryn na potrzeby kradzieży.

Aby zapewnić optymalne bezpieczeństwo i korzystać z zalet CORB, zalecamy:

  • 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).
  • Wycofanie zgody na podsłuchiwanie za pomocą 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 izolacja witryn jest ważna dla programistów?

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ą witryny, a poniżej omawiamy najważniejsze z nich.

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.

Weźmy na przykład witrynę fluffykittens.example, która komunikuje się z widżetem społecznościowym hostowanym na stronie 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. Następnie strona FluffyKittens zmienia szerokość na 456 pikseli (uruchamia układ) 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? Zanim w Chrome włączono izolację witryn, odpowiedź brzmiała 456. Dostęp do document.documentElement.clientWidth wymusza układ, który przed włączeniem izolacji witryn w Chrome był synchroniczny. Jednak po włączeniu izolacji witryn ponowne układanie widżetu społecznościowego z innych domen odbywa się teraz asynchronicznie w oddzielnym procesie. Dlatego odpowiedzią może być też 123, czyli stara wartość width.

Jeśli strona zmieni rozmiar elementu <iframe> w innej domenie i wyśle do niego element postMessage, w ramach izolacji witryny odbiorczy element może jeszcze nie znać nowego rozmiaru, gdy otrzyma wiadomość. Ogólnie może to spowodować błędy na stronach, jeśli zakładają one, że zmiana układu jest natychmiast rozpowszechniana we wszystkich ramach na stronie.

W tym konkretnym przykładzie bardziej niezawodne rozwiązanie polegałoby 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 przejdzie do innej strony lub zostanie zamknięta, stary dokument oraz wszystkie zapisane w nim dokumenty podrzędne będą miały wykonywany swój unload. Jeśli nowa nawigacja odbywa się w tym samym procesie renderowania (np. w przypadku nawigacji w ramach tego samego źródła), unloadobsługa starego dokumentu i jego ramek podrzędnych może działać przez dowolnie długi czas, zanim pozwoli na zatwierdzanie 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 old.example sterowniki rozładowania old.example i jej ramek podrzędnych działają w tle w procesie old.example, a po wyświetleniu strony new.example stare sterowniki 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 rezultacie powyższa sytuacja występuje w większej liczbie przypadków, a obsługa wyjęcia z pamięci w <iframe> często ma opisane powyżej zachowania dotyczące działania w tle i czasu 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 w przypadku izolowania witryn moduły obsługi wyładowania działają 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 też świadomi błędów, w których przypadku moduły obsługi wyładowania podramek nie są jeszcze w stanie korzystać z pewnych funkcji. Pracujemy nad ich rozwiązaniem.

Ważnym przypadkiem dla modułów obsługi wyładowywania jest wysyłanie pingów na koniec sesji. Zwykle odbywa się to w ten 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 niesprawdzonym witrynom uzyskiwanie dostępu do Twoich kont w innych witrynach lub kradzież informacji z tych kont. Dzieje się tak, ponieważ każda witryna jest izolowana w ramach własnego procesu. W ramach tego procesu CORB stara się nie przekazywać zasobów danych wrażliwych do procesu renderowania. Dzięki tym rekomendacjom w pełni wykorzystasz możliwości nowych funkcji zabezpieczeń.

Dziękujemy Alexowi Moshchukowi, Charliemu Reisowi, Jasonowi Millerowi, Nasko Oskovowi, Philipowi Waltonowi, Shubhiemu Panickerowi i Thomasowi Steinerowi za przeczytanie wersji roboczej tego artykułu i przesłanie opinii.