O co chodzi w SameSite w ciasteczkach?
January 25, 2020 ‐ 10 minut(a) czytania
Ciasteczka są jedną z metod dodawania trwałych stanów do witryn i aplikacji webowych. Przez lata ich możliwości rozszerzały się i ewoluowały, jednocześnie ciągnąć przez cały ten czas pewne problemy platformowe. Aby je zaadresować, przeglądarki (włączając w to Chrome, Firefox, Edge i ich pochodne) zmieniają zachowanie tak, by domyślnie egzekwować bardziej rygorystyczne zasady zachowania prywatności.
Każde ciasteczko to para klucz=wartość
wraz z szeregiem atrybutów kontrolujących kiedy i gdzie ciasteczko może być używane. Jeśli tworzycie lub administrujecie witrynami i aplikacjami webowymi, prawie na pewno używaliście już tych atrybutów, aby ustawić daty ważności lub wymusić przesłanie ciasteczka kanałem HTTPS. Serwery przesyłają ciasteczka używając nagłówka Set-Cookie
w ramach odpowiedzi HTTP. Wszystkie szczegóły są opisane w RFC6265bis, ale poniżej znajdziecie skrótowe wyjaśnienia oparte na przykładach.
Powiedzmy, że macie bloga, na którym chcecie wyświetlić powiadanie “Co nowego” waszym czytelnikom. Użytkownicy mogą odrzucić powiadomienie, dzięki czemu nie zobaczą go ponownie przez jakiś czas. Ich wybór możemy przechować w ciasteczku, które będzie ważne przez miesiąc (na potrzeby tego przykładu, ustalmy, że miesiąc ma 2 600 000 sekund) i że będzie przesłane przez HTTPS. Wówczas nagłówek powinien wyglądać następująco:
Set-Cookie: promo_shown=1; Max-Age=2600000; Secure
Kiedy na blogu pojawia się czytelnik, który spełnia wskazane kryteria, trzymając się przykładu ma bezpieczne połączenie i ciasteczko ma nie więcej niż 1 miesiąc, jego przeglądarka prześle następującą informację w nagłówku żądania:
Cookie: promo_shown=1
Ciasteczka dla tej witryny można dodawać oraz odczytywać za pomocą JavaScript, używając właściwości document.cookie
. Odpowiednia instrukcja document.cookie
stworzy lub nadpisze ciasteczko ze wskazanym kluczem. Przykładowo, możecie wykonać poniższy kod w konsoli JavaScript waszej przeglądarki:
> document.cookie = "promo_shown=1; Max-Age=2600000; Secure"
< "promo_shown=1; Max-Age=2600000; Secure"
Aby otworzyć konsolę należy wcisnąć F12
i wybrać kartę konsoli JavaScript. W oknie Console będą znajdowały się zdarzenia związane z wykonanie kodu JS w otwartej karcie, a dodatkowo będziemy mogli wykonać własny kod. Wykonajcie test i dodajcie ciasteczko.
Wprowadźcie przykładowy kod, który zainstaluje ciasteczko dla aktywnej sesji document.cookie="pfeacademy=favorite"
lub użyjcie zaawansowanych opcji, document.cookie="username=Grzegorz; expires=Thu, 31 Dec 2020 12:00:00 UTC; path=/";
.
Z użyciem document.cookie
możliwe jest też odczytanie i przetworzenie ciasteczek w aktualnym kontekście i bezpośrednio po stronie klienta.
W konsoli wpiszcie document.cookies;
, wynik będzie zawierał wszystkie ciasteczka oddzielone przecinkiem:
> document.cookie;
< "promo_shown=1; username=Grzegorz; pfeacademy=favorite"
Większość witryn instaluje w pamięci podręcznej przeglądarki przynajmniej kilka ciasteczek. Ciasteczka te są następnie przesyłane przy każdym żądaniu klienta dla tej domeny, co ma kilka skutków. Przepustowość ruchu wychodzącego (upload) jest zwykle bardziej ograniczona niż przychodzącego (download), generuje to dodatkowy narzut dla wszystkich żądań wychodzących i skutkuje pewnymi opóźnieniami w zasadzie od pierwszego bajtu komunikacji. Zachowajcie ostrożność w liczbie i rozmiarze ustawianych plików cookie. Koniecznie stosujcie atrybut Max-Age
, aby zapewnić ich przetwarzanie tylko tak długo jak to konieczne.
Co to są własne i zewnętrzne ciasteczka?
Jeśli przyjrzycie się ciasteczkom zainstalowanym podczas otwarcia przykładowej witryny, zauważycie, że przynajmniej część pochodzi z innych (niż aktualnie otwarta) stron. W konsoli przejdźcie na kartę Application, następnie do sekcji Storage i grupy Cookies.
Na moim blogu znajdziecie komponenty udostępnione z serwisów Linkedin i Twitter, własne dodatki wymusza także platforma, z której korzystam, czyli Tumblr. Wszystkie one zainstalowały pewną liczbę ciasteczek.
Ciasteczka, których adres pasuje do domeny wyświetlanej witryny, to tak zwane ciasteczka “własne” (first-party), te instalowane z innych adresów nazywane są “zewnętrznymi” (third-party). Podział na ciasteczka własne i zewnętrzne nie jest absolutny i zależy od kontekstu użytkownika, to samo ciasteczko może być first-party lub third-party zależnie od witryny, na której znajduje się użytkownik w danym czasie.
Wasza przeglądarka na pewno daje możliwość zarządzania ciasteczkami oraz przeglądu wszystkich plików zapisanych w pamięci podręcznej.
Wróćmy na chwilę do przykładu promo_shown. Powiedzmy, że jeden z postów na blogu zawiera baner w pliku /blog/img/azure.png
. Ponieważ to bardzo fajny obrazek, inni użytkownicy wyświetlają go używając odnośnika bezpośredniego. Jeśli czytelnik był na moim blogu i ma już ciasteczko promo_shown
, wówczas kiedy wyświetla azure.png
na innej witrynie, to ciasteczko zostanie załączone do żądania pobrania obrazka. Przyznacie, że nie jest to specjalnie użyteczne, bo promo_shown
nie jest wykorzystywane nigdzie poza moją witryną, zwiększa tylko rozmiar żądania.
Jeśli efekt nie jest zamierzony, to po co w ogóle go wywoływać? Ciasteczka dostarczają witrynom i aplikacjom webowym możliwości zachowania stanu, w ich kontekście. Przykładowo, jeśli zagnieździcie wideo z serwisu YouTube, odwiedzający, którzy zdecydują się je uruchomić, będą mogli skorzystać z opcji “Zobacz później” w odtwarzaczu. Jeśli wasz czytelnik jest już zalogowany w YouTube, to jego sesja jest rozciągana do osadzonego odtwarzacza z użyciem zewnętrznego ciasteczka — co oznacza, że przycisk “Zobacz później” zapisze wideo na liście odtwarzania, bez konieczności przechodzenia przez logowania (co przekierowałoby użytkownika z bieżącej witryny do YouTube i wymagało jego powrotu).
Otwartość jest jedną z wbudowanych,
immanentnych
właściwości sieci WWW. Dzięki temu, wiele osób i organizacji tworzy własne treści i aplikacje. Otwartość podnosi również szereg obaw związanych z bezpieczeństwem i prywatnością. Ataki polegające na fałszowaniu żądań w różnych witrynach (cross-site request forgery, CSRF) polegają na tym, że ciasteczka są dołączane do każdego żądania do danego źródła, niezależnie od tego, kto je inicjuje. Przykładowo, kiedy odwiedzacie zlosliwa.strone
, może ona wywołać żądanie do innej.strony
, a wasza przeglądarka z przyjemnością załączy powiązane pliki cookies. Jeżeli nasza strona (lub zabezpieczenia przeglądarki), nie jest ostrożna w sprawdzaniu poprawności tych żądań, wówczas zlosliwa.strona
może wykonywać czynności wynikające ze stanu przekazanego w ciasteczkach (np. usuwać artykuły lub dodawać treści na blogu).
Ostatnie lata znacznie poprawiły poziom świadomości użytkowników w temacie instalacji ciasteczek oraz tego jak mogą być wykorzystane do śledzenia ich aktywności w wielu witrynach. Jednak do tej pory nie było sposobu, aby jednoznacznie określić intencję czy zakres użycia ciasteczka. Raz jeszcze wróćmy do przykłady z ciasteczkiem promo_shown
, które powinno być wysłane tylko w kontekście własnym (w komunikacji ze stroną, która go zainstalowała), podczas gdy sesyjne ciasteczko dla widżetu, który jest osadzony w innych witrynach, powinno funkcjonować jako zewnętrzne.
Wyraźne określenie możliwości użycia ciasteczka za pomocą atrybutu SameSite
Wprowadzenie atrybutu SameSite
(zdefiniowanego w RFC6265bis) pozwala zadeklarować czy ciasteczko powinno być ograniczone do kontekstu strony, która go wydała lub tego określonego przez wartość same-site. Bardzo ważne jest, aby zrozumieć co oznacza “strona”. Strona jest połączniem sufiksu domenowego oraz podmiotu lub subdomeny w tej domenie. Przykładowo, witryna blog.pfe.academy
jest częścią domeny pfe.academy
.
Kluczowe pojęcie: Jeśli użytkownik otworzył blog.pfe.academy
i zażądał obrazka z static.pfe.academy
to jest to żądanie typu same-site.
Definiuje to lista publicznych sufiksów, nie jest to tylko lista domen głównych jak .com
, ale także usług jak na przykład github.io
. A to pozwala, aby moj-projekt.github.io
i twoj-projekt.github.io
były identyfikowane jako osobne strony.
Kluczowe pojęcie: Jeśli użytkownik na twoj-projekt.github.io
zażąda obrazka z moj-projekt.github.io
to jest to żądanie pomiędzy stronami (cross-site request).
Wprowadzenie atrybutu SameSite
w ciasteczku dostarcza trzech sposobów kontrolowania zachowania użycia ciasteczka. Można nie ustawiać wartości, można użyć wartości Strict
lub Lax
, aby ograniczyć żądania do same-site.
Jeśli SameSite
zostanie ustawione na Strict
, takie ciasteczko zostanie przesłane tylko do witryny, która je zainstalowała (first-party context). W kategoriach użytkownika plik cookie zostanie wysłany tylko wtedy, gdy witryna dla pliku cookie jest zgodna z witryną aktualnie wyświetlaną na pasku adresu URL przeglądarki. Poniżej przykład, jak zainstalować ciasteczko promo_shown
w ten sposób:
Set-Cookie: promo_shown=1; SameSite=Strict
Jeśli użytkownik jest na naszej stronie, ciasteczko zostanie przesłane w żądaniu. Jednak po kliknięciu linku do Twojej witryny, powiedzmy z innej witryny lub za pośrednictwem wiadomości e-mail od znajomego, na pierwsze żądanie plik cookie nie zostanie wysłany. To jest szczególnie ważne, kiedy pliki cookie powiązane są z jakąś funkcjonalnością (np. zmianą hasła lub zakupem), która zawsze będzie znajdować się poza początkową komunikacją, ale jest zbyt restrykcyjna dla promo_shown
. Jeśli czytelnik podąża za linkiem z innej strony, chce, aby plik cookie został wysłany, aby można było zastosować wcześniejsze preferencje.
Wtedy właśnie zastosowanie ma SameSite=Lax
, umożliwiając przesyłanie ciasteczek przy żądaniach kierowanych przez strony w tej samej przestrzeni domenowej. Raz jeszcze
przyjrzyjmy
się przykładowi z banerem, w którym inna witryna odwołuje się do naszej zawartości. Jeśli w witrynie osadzono grafikę bezpośrednio z odnośnikiem do artykuły.
<p>Panie! Tenanty prawie za darmo!</p>
<img src="https://pfe.academy/blog/img/azure.png" />
<p>Czytaj <a href="https://pfe.academy/blog/azure.html">więcej</a>...</p>
A ciasteczko zostało ustawione następująco:
Set-Cookie: promo_shown=1; SameSite=Lax
Kiedy czytelnik jest na zewnętrznej (względem pfe.academy) witrynie ciasteczko nie zostanie wysłane wraz z żądaniem pliku azure.png
. Jednak, kiedy zostanie uruchomiony odnośnik azure.html
na naszym blogu, to żądanie będzie już zawierać ciasteczko. Z tej perspektywy wybór trybu Lax
będzie dobry dla ciasteczek wpływających na wyświetlania jakiś elementów, a tryb Strict
dla ciasteczek powiązanych z obsługą akcji użytkownika.
Uwaga: Ani Strict
ani Lax
nie są kompletnym rozwiązanie dla bezpieczeństwa naszej witryny. Ciasteczka są wysyłane jako część żądania użytkownika i należy traktować je tak samo, jak inne dane wejściowe użytkownika. W ramach dobrych praktyk, oznacza to sprawdzanie poprawności danych wejściowych. Nigdy nie używajcie plików cookie do przechowywania danych, które
po stronie serwera
uważacie za sekrety (np. hasło).
Na koniec zostawiliśmy możliwość nieokreślania wartości, co wcześniej było sposobem na niejawne stwierdzenie, że chcemy, aby plik cookie był wysyłany we wszystkich kontekstach. W RFC6265bis zmieniono podejście i teraz wymaga to zastosowania ustawienia SameSite=None
. Oznacza to, że możecie użyć wartości None
, aby zezwolić na wysyłanie ciasteczka w kontekście strony trzeciej.
Jeśli świadczycie usługę, z której korzystają inne witryny, taką jak widżety, osadzone treści, programy partnerskie, reklamy lub logowanie do wielu witryn (zwracam uwagę na usługi federacyjne Active Directory Federation Services lub w Azure), powinniście użyć wartości None
, aby jawnie wskazać otwarty zakres użycia.
Zmiana domyślnego zachowania dla ciasteczek bez wartości SameSite
Chociaż atrybut SameSite
jest szeroko obsługiwany, niestety nie został powszechnie zaadoptowany przez programistów. Domyślnie otwarte przesyłanie ciastek wszędzie działa bezobsługowo, ale naraża użytkownika na CSRF oraz niezamierzone wycieki informacji. Aby zachęcić deweloperów do określania jak przeglądarka powinna korzystać z ciasteczka, aby zapewnić bezpieczne korzystania z witryny lub aplikacji webowej użytkownikowi, IETF zaproponowało w Incrementally better cookies dwie kluczowe zmiany:
- Ciasteczka bez atrybutu
SameSite
będą traktowane jak z wartościąSameSite=Lax
. - Ciasteczka z atrybutem
SameSite=None
muszą jednocześnie być instalowane w trybieSecure
, co oznacza, że wymagają bezpiecznego kontekstu.
Chromium (i jego pochodne, w tym Google Chrome oraz nowy Microsoft Edge) implementuje zmiany od wersji oznaczonej numerem 80. Mozilla udostępniła je testowo w Firefoksie 69 i zamierza je włączyć domyślnie w przyszłości.
Podsumujmy dotychczasową dyskusję na ten temat.
SameSite=Lax
Default
Atrybut nie został ustawiony jawnie.
Set-Cookie: promo_shown=1
Jeśli ciasteczko nie zostało opatrzone żadnym atrybutem SameSite
zostanie użyte domyślne dla przeglądarki zachowanie, równoznaczne z następującym.
Set-Cookie: promo_shown=1; SameSite=Lax
Przeglądarka potraktuje ciasteczko tak samo jakby wskazano SameSite=Lax
.
Chociaż ma to na celu zastosowanie bezpieczniejszego ustawienia domyślnego, najlepiej ustawić jawny atrybut SameSite
zamiast polegać na przeglądarce, bo różne aplikacje mogą to różnie realizować. Pamiętajcie też, że klientami waszej aplikacji mogą być też inne aplikacje, np. Outlook, Word, Excel, Teams (tutaj jest to aplikacja osadzona w Electrone i wyświetlana w silniku Chromium), prawie wszystkie aplikacje na Androidzie wykorzystują WebView, a jak zachowają się inne? Najlepiej jawnie wskazać bariery użycia ciasteczka, aby wszędzie były realizowane spójnie.
Uwaga: Domyślne zachowanie stosowane przez Chrome jest nieco bardziej liberalne niż jawne wskazanie SameSite=Lax
ponieważ pozwoli na wysyłanie niektórych plików cookie na żądania POST najwyższego poziomu. Dokładne szczegóły można znaleźć w ogłoszeniu blink-dev. To tymczasowe rozwiązanie mające na celu złagodzenie objawów na niedostosowanych na czas witrynach i aplikacjach. Najlepiej jednak zezwolić na cross-site cookies za pomocą SameSite=None; Secure
.
SameSite=None
Musi być instalowane w bezpiecznym kontekście z użyciem parametru secure.
Rejected
Set-Cookie: widget_session=abc123; SameSite=None
Instalacja ciasteczka bez Secure
będzie odrzucona.
Accepted
Set-Cookie: widget_session=abc123; SameSite=None; Secure
Musiecie upewnić się, że atrybut SameSite=None
idzie w parze z Secure
.
W przeglądarkach opartych Chromium w wersji 76 lub późniejszych można włączyć testowo nowe zachowanie włączając flagę pod adresem chrome://flags/#cookies-without-same-site-must-be-secure, w Edge
edge://flags/#cookies-without-same-site-must-be-secure, a w
Firefoksie 69 w about:config
za pomocą ustawienia network.cookie.sameSite.noneRequiresSecure
.
Wydaje mi się, że będziecie chcieli przełączyć wskazane ustawienia podczas instalacji nowych plików cookie i odświeżaniu istniejących ciasteczek, nawet jeśli nie zbliżają się one do ich daty ważności.
Jeśli korzystacie z usług dostarczających treści zewnętrznych pod waszą stroną, powinniście sprawdzić czy dostawca przygotował się do zmian. Konieczne może być zaktualizowanie zależności lub osadzonych fragmentów, aby upewnić się, że witryna rozpoznaje nowe zachowanie.
Obie zmiany są wstecznie kompatybilne z przeglądarkami, które poprawnie zaimplementowały poprzednią wersję atrybutu SameSite
lub po prostu go nie obsługują. Stosując te zmiany do plików cookie, wyraźnie zaznaczacie ich przeznaczenie, zamiast polegać na domyślnym zachowaniu przeglądarki. Podobnie każdy klient, który nie rozpoznaje SameSite=None
na razie, powinien go zignorować i kontynuować tak, jakby atrybut nie został ustawiony.
Ostrzeżenie: Wiele starszych wersji przeglądarek, w tym Chrome, Safari i UC, jest niezgodnych z nowym atrybutem None
i może zignorować lub ograniczyć użycie ciasteczek. To zachowanie jest ustalone w aktualnych wersjach, ale należy sprawdzić ruch, aby określić, na jaki odsetek użytkowników będzie to miało wpływ. Listę znanych niekompatybilnych klientów można zobaczyć na stronie projektu Chromium.
Ten artykuł otwiera serię, w następnej części napiszę trochę o tym jak konfigurować instalację ciasteczek w różnych scenariuszach, później o wpływie na logowanie za pośrednictwem usług federacyjnych Active Directory Federation Services (i zmianach w styczniowych poprawkach dla Windows Server) oraz w Azure, a na koniec o tym jak przygotować klientów za pomocą GPO, Intune i MDM
Wpis jest w znacznej części oparty na artykule SameSite cookies explained.
Twoje okno na chmurę