Bezpieczeństwo, Frontend

Cookie. Jak zastosować atrybuty domain i path

Szczegółowe opisy niektórych mechanizmów mogą być dla nas nie do końca zrozumiałe, póki sami ich nie wypróbujemy. Dlatego postanowiłem stworzyć podsumowanie pokazujące różne sposoby użycia (w tym również niepoprawnego) Cookie z atrybutami *domain* i *path*. Oprócz tego, pokazałem ich wpływ na bezpieczeństwo aplikacji i możliwość wykorzystania przy różnych atakach.

Michał Dardas. Pentester w LogicalTrust. Na co dzień atakuje i psuje aplikacje, przede wszystkim webowe oraz mobilne. Ex-programista z niegasnącym zamiłowaniem do technologii Javowych. We współpracy z Zaufaną Trzecią Stroną prowadzi szkolenia poświęcone bezpieczeństwu aplikacji webowych oraz mobilnych. Autor artykułu pt. Poznajcie Needle, narzędzie do analizy aplikacji mobilnych iOS.


Protokół HTTP jest bezstanowy, co oznacza, że w podstawowej formie nie istnieje powiązanie między kolejnymi żądaniami. W efekcie serwer, który je odbiera nie jest w stanie rozpoznać od jakiego klienta pochodzą. Popularnym rozwiązaniem jest wykorzystanie mechanizmu Cookie. Pozwala on serwerowi zapisać w przeglądarce użytkownika niewielkie porcje danych. Przeglądarka przy każdym kolejnym żądaniu do serwera załącza przechowywane Cookie. Dzięki temu serwer jest w stanie rozpoznawać, który użytkownik się z nim komunikuje.

Nie jest to zresztą jedyne zastosowanie dla tego mechanizmu. Cookie jest też często wykorzystywane do przechowywania wartości po stronie użytkownika, np. mniej znaczących ustawień.

Przy standardowym wykorzystaniu Cookie, przeglądarka dba o to, żeby w żądaniu załączać Cookie pochodzące z domeny, z którą się komunikuje. Jednak Cookie oferuje możliwość ustawienia specjalnych atrybutów `domain` i `path`. Przyjrzyjmy się jak zachowują się one w praktyce i jaki to może mieć wpływ na bezpieczeństwo.

Sposób w jaki Cookie powinno być obsługiwane przez przeglądarki jest zdefiniowany w dokumencie RFC6265.

Domain

Jak wyżej, Cookie domyślnie powiązywane jest ściśle z domeną, która je ustawiła. Jednak stosując atrybut `domain`, możemy utworzyć Cookie również subdomenom (z pewnymi ograniczeniami). Szczegóły dotyczące tego atrybutu można znaleźć w sekcjach 4.1.2.3 i 5.2.3 dokumentu RFC6265.

Załóżmy, że nasz serwer znajduje się pod adresem `http://mail.example.com` i ustawia zestaw różnych Cookie, z różnymi kombinacjami opcji `domain`. Część z tych ustawień nie jest poprawna. Jest to celowy zabieg w celu zaprezentowania nie tylko co działa, ale również co nie działa.

GET / HTTP/1.1
Host: mail.example.com


HTTP/1.1 200 OK
Set-Cookie: mailsession0=0000
Set-Cookie: mailsession1=1111; domain=example.com
Set-Cookie: mailsession2=2222; domain=.example.com
Set-Cookie: mailsession3=3333; domain=com
Set-Cookie: mailsession4=4444; domain=.com
Set-Cookie: mailsession5=5555; domain=another.com
Set-Cookie: mailsession6=6666; domain=.another.com
Set-Cookie: mailsession7=7777; domain=mail.example.com
Set-Cookie: mailsession8=8888; domain=.mail.example.com
Set-Cookie: mailsession9=9999; domain=test.mail.example.com
Set-Cookie: mailsessiona=aaaa; domain=.test.mail.example.com
Set-Cookie: mailsessionb=bbbb; domain=shop.example.com
Set-Cookie: mailsessionc=cccc; domain=.shop.example.com
Set-Cookie: mailsessiond=dddd; domain=test.shop.example.com
Set-Cookie: mailsessione=eeee; domain=.test.shop.example.com
  • Cookie mailsession0 nie posiada ustawienia domain. Oznacza to, że będzie znajdowało się tylko w żądaniach do domeny mail.example.com.
  • mailsession1 i mailsession2 ustawiają Cookie dla domeny macierzystej i jej wszystkich subdomen. Oznacza to, że będą one znajdować się we wszystkich żądaniach do `example.com` i jej wszystkich subdomen (`*.example.com`).
  • mailsession3 i mailsession4 ustawiane są dla domeny macierzystej najwyższego poziomu, przez co zostaną zignorowane.
  • mailsession5 i mailsession6 ustawiane są dla zupełnie innej domeny i one też zostaną zignorowane.
  • mailsession7 i mailsession8 są ustawiane dla aktualnej domeny. W odróżnieniu od `mailsession0`, będą one trafiać również do żądań odnoszących się do subdomen tej domeny.
  • mailsession9 i mailsessiona są ustawiane dla subdomeny aktualnej domeny. Jest to niepoprawne ustawienie i zostaną zignorowane.
  • mailsessionb, mailsessionc, mailsessiond i mailsessione to próba ustawienia Cookie dla innych subdomen należących do domeny macierzystej. Te również są niepoprawne i zostaną zignorowane.

Zobaczmy to teraz na przykładzie żądań do różnych domen. Żądania były wykonywane z przeglądarki Firefox ESR 60.4.0 i to ona decydowała o dołączeniu wybranych Cookie. Po wszystkich przeglądarkach podążających za standardem powinniśmy spodziewać się takiego samego zachowania.

GET / HTTP/1.1
Host: example.com
Cookie: mailsession1=1111; mailsession2=2222


GET / HTTP/1.1
Host: another.com


GET / HTTP/1.1
Host: mail.example.com
Cookie: mailsession0=0000; mailsession1=1111; mailsession2=2222; mailsession7=7777; mailsession8=8888


GET / HTTP/1.1
Host: test.mail.example.com
Cookie: mailsession1=1111; mailsession2=2222; mailsession7=7777; mailsession8=8888


GET / HTTP/1.1
Host: shop.example.com
Cookie: mailsession1=1111; mailsession2=2222


GET / HTTP/1.1
Host: test.shop.example.com
Cookie: mailsession1=1111; mailsession2=2222

Tabelka podsumowuje jak ustawienie *domain* wpływa na dołączanie Cookie do żądań do konkretnych domen. Cookie ustawiane przez domenę mail.example.com.

Public suffix

Rozwijając jeszcze temat poruszony przy mailsession3 i mailsession4, Cookie możemy ustawiać dla dowolnej nadrzędnej domeny, dopóki nie jest to domena należąca do public suffix, czyli listy domen, które pozwalają na publiczne rejestrowanie subdomen. Do takiej listy należą m.in. domeny com, com.pl, pl, co.uk. Taka lista może zmieniać się z czasem, a jej aktualna wersja jest dostępna pod adresem <https://publicsuffix.org/list/>.

Można to dodatkowo zaobserwować na poniższym przykładzie.

GET / HTTP/1.1
Host: a.b.c.d.e.f.example.com


HTTP/1.1 200 OK
Set-Cookie: a=1; domain=com
Set-Cookie: b=2; domain=example.com
Set-Cookie: c=3; domain=f.example.com

Odpowiedź z domeny `a.b.c.d.e.f.example.com` ustawia Cookie `b` i `c` dla domen `example.com` i `f.example.com` (i mogłaby też dla kolejnych). A próba ustawienia Cookie dla `public suffix` się nie powiedzie.

GET / HTTP/1.1
Host: f.example.com
Cookie: c=3; b=2

Atak

Wiedza na temat zachowania atrybutu domain może być przydatna w kilku specyficznych atakach. W tym momencie przyda nam się jeszcze jedna informacja. Cookie może być nie tylko ustawiane przez sam serwer (nagłówkiem Set-Cookie), ale również za pomocą JavaScriptowego atrybutu document.cookie.

Załóżmy, że we wspomnianej już aplikacji mail.example.com znajduje się podatność typu XSS, czyli jako atakujący możemy wstrzykiwać dowolny kod JavaScript w zawartość strony. Dodatkowo załóżmy, że chcemy nasz atak przenieść z początkowej domeny (mail.example.com) na inną powiązaną domenę — w tym przykładzie niech to będzie shop.example.com.

Czyli moglibyśmy na stronie mail.example.com wstrzykiwać złośliwy kod, który ustawiałby Cookie dla example.com i jej wszystkich subdomen.

document.cookie="NAME=VALUE; domain=example.com";

A w jakich sytuacjach mogłoby to być przydatne?

1. Podatności typu Cookie based (na przykład Cookie based SQL injection albo Cookie based XSS). To rodzaj podatności, gdzie złośliwy ładunek (payload) pochodzi z Cookie. Na przykład w równoległej subdomenie shop.example.com mógłby znajdować się taki podatny skrypt:

<?php
(...)
echo "Hello " . $_COOKIE['username'];
(...)

W takiej sytuacji, następujący kod JavaScript (uruchamiany z poziomu domeny mail.example.com) mógłby taką podatność wykorzystywać w celu przeprowadzenia kolejnego ataku XSS, tym razem w kontekście domeny shop.example.com.

document.cookie="username='<script>alert(1)</script>'; domain=example.com";

2. Podatność typu Self XSS. W przypadku tego typu podatności ofiara sama musi przeprowadzić na sobie atak (sama musi wprowadzić do aplikacji payload). Jednym z wariantów tego rodzaju podatności jest sytuacja, gdy payload XSS znajduje się w polu, które widzi tylko jeden konkretny użytkownik, np. pole ze swoją nazwą użytkownika w opcjach. Wydawać by się mogło, że taki rodzaj podatności nie ma praktycznego zastosowania. Atakujący może jednak przygotować specjalne konto w domenie shop.example.com, na którym umieści payload (na przykład w imieniu), zaloguje się na nie w celu wygenerowania Cookie sesyjnego. A następnie za pomocą pierwotnego XSSa w mail.example.com zaloguje swoją ofiarę na takie konto. Dzięki temu atakujący mógłby wykradać dane z kont użytkowników shop.example.com.

document.cookie="sessionid='1234567890'; domain=example.com";

W rzeczywistości podobne ataki były wycelowane m.in. w użytkowników Ubera.

3. Akcje phisingowe. Jest to technicznie prostsza odmiana punktu 2, która nie opiera się na obecności żadnej dodatkowej podatności. Polega na zalogowaniu ofiary na podstawione konto, licząc, że ofiara się nie zorientuje i wprowadzi na nim wrażliwe dane np. zapisze swoją kartę kredytową.

Po drodze pojawia się jeszcze jeden problem. Jeśli domena shop.example.com ustawiła swoje Cookie, to próbując je nadpisać z poziomu mail.example.com za pomocą kodu

document.cookie="sessionid=<falszywy_identyfikator>; domain=example.com";

sprawimy, że przeglądarka będzie do serwera wysyłać dwa Cookie o tej samej nazwie. To oryginalnie ustawione przez serwer oraz nasze ustawiane z poziomu JavaScriptu. Jest to dla nas niepożądane zachowanie, bo nie mamy gwarancji, które z tych dwóch Cookie serwer weźmie pod uwagę. Z tego powodu nasza próba, np. nadpisania identyfikatora sesji, może się nie powieść.

Może to wyglądać tak:

GET / HTTP/1.1
Host: shop.example.com
Cookie: sessionid=<oryginalny_identyfikator>; sessionid=<falszywy_identyfikator>

Można to rozwiązać za pomocą sztuczki zwanej *cookie jar overflow*. Polega ona na stworzeniu tak wielu Cookie, że przeglądarka usunie te najstarsze, a w tym to oryginalnie ustawione przez serwer.

Wykonując taki kod:

for (var i = 0; i < 2000; i++) { 
document.cookie = "cookie_" + i + "=1; domain=example.com"; 
} 
for (i = 0; i < 2000; i++) { 
document.cookie = "cookie_" + i + "=1; domain=example.com;expires=Thu, 01 Jan 1970 00:00:00 GMT"; 
}
document.cookie="sessionid=<falszywy_identyfikator>; domain=example.com";

sprawimy, że przy kolejnym żądaniu przeglądarka dołączy tylko nasze, złośliwe Cookie.

Zależnie od przeglądarki ofiary, może być wymagana inna liczba iteracji.

GET / HTTP/1.1
Host: shop.example.com
Cookie: sessionid=<falszywy_identyfikator>

Path

Kolejnym atrybutem wartym uwagi jest *path*. Pozwala on ograniczyć zakres Cookie do konkretnego katalogu na serwerze. Może to być wykorzystane np. do separacji Cookie między kilkoma aplikacjami dostępnymi na tej samej domenie. Szczegóły dotyczące tego atrybutu można znaleźć w sekcjach 4.1.2.4 i 5.2.4 dokumentu RFC6265.

Załóżmy, że na serwerze example.com mamy uruchomione dwie aplikacje, pierwsza dostępna pod adresem example.com/tasks, a druga example.com/notes. Chcemy, żeby te dwie aplikacje nie widziały między sobą Cookie. Może to zostać osiągnięte w następujący sposób:

GET / HTTP/1.1
Host: example.com


HTTP/1.1 200 OK
Set-Cookie: global=1
Set-Cookie: global_2=2; path=/
Set-Cookie: tasks_session=333; path=/tasks
Set-Cookie: notes_session=444; path=/notes

Serwer ustawia następujące Cookie:

  • global i global_2, które będą dołączane do wszystkich żądań,
  • tasks_session, który będzie dołączany do żądań odnoszących się do katalogu /tasks i jego podkatalogów np. /tasks/1, /tasks/new itd.
  • notes_session, który analogicznie do powyższego, będzie dołączany do żądań odnoszących się do katalogu /notes i jego podkatalogów

Możemy to zaobserwować w następujących żądaniach:

GET / HTTP/1.1
Host: example.com
Cookie: global=1; global_2=2


GET /tasks HTTP/1.1
Host: example.com
Cookie: tasks_session=333; global=1; global_2=2


GET /tasks/new HTTP/1.1
Host: example.com
Cookie: tasks_session=333; global=1; global_2=2


GET /notes HTTP/1.1
Host: example.com
Cookie: notes_session=444; global=1; global_2=2


GET /error HTTP/1.1
Host: example.com
Cookie: global=1; global_2=2

Można to podsumować za pomocą tabelki.

Czy taki mechanizm ma jakikolwiek wpływ na bezpieczeństwo? Czy w przypadku obecności podatności typu XSS, konfiguracja path uchroniłaby pozostałe aplikacje? Sprawdźmy to na omawianym przykładzie. Załóżmy, że w aplikacji example.com/tasks istnieje taka podatność i atakujący ma możliwość wstrzyknięcia dowolnego kodu. Jeżeli próbowałby odczytać Cookie po prostu odwołując się do document.cookie to notes_session nie byłby dla niego widoczny (nie wspominając już o ustawieniu *httponly*, które mogłoby uniemożliwić odczyt któregokolwiek z Cookie). Jednak taka sytuacja nie sprawia, że atakujący nie ma innych możliwości.

Najprostsze, co można w takiej sytuacji zrobić, to stworzyć iframe odwołujący się do katalogu innej aplikacji. Ze względu na to, że zmienia się tylko nazwa katalogu, to nie mają tutaj zastosowania restrykcje narzucane przez Same-Origin Policy, który porównuje schemat, hosta i numer portu (one pozostają bez zmian). W efekcie przeglądarka załączy Cookie powiązane z aplikacją, a atakujący będzie mógł wykonywać dowolne żądania w imieniu zalogowanego użytkownika i odczytywać odpowiedzi, po to żeby np. wykradać wrażliwe dane użytkownika albo odczytać token CSRF z kodu HTML.

Payload dla XSS z poziomu http://example.com/tasks.

var fr = document.createElement("iframe");
fr.src = "//example.com/notes/12";
document.body.appendChild(fr);
console.log(fr.contentWindow.document.body.innerHTML);

Podsumowanie

Zapoznaliśmy się ze sposobem działania atrybutów domain i path. *Domain* pozwala przypisywać Cookie również dla domeny macierzystej i subdomen (wyjątkiem jest *public suffix*). Nawet jeżeli nie wykorzystujemy tego atrybutu, warto mieć na uwadze, że atakujący wykorzystujący podatność XSS może za jego pomocą poszerzać zasięg swoich ataków.

*Path* pozwala ograniczyć zakres Cookie do wybranego katalogu na serwerze. Ograniczenie to nie pełni funkcji związanej z bezpieczeństwem i może być łatwo ominięte przez atakującego. Dlatego udostępnianie wielu aplikacji z poziomu jednej domeny nie jest najlepszym pomysłem.

W omawianych przypadkach źródłem problemu były potencjalne błędy typu XSS, dlatego to od ich eliminacji powinno rozpocząć się dbanie o aplikację. Jednak wiedza na temat możliwości atakującego może dodatkowo podnieść naszą świadomość i skuteczność w tym zakresie.


Zdjęcie główne artykułu pochodzi z pexels.com.

Wraz z Tomaszem Gańskim jestem współtwórcą justjoin.it - największego job boardu dla polskiej branży IT. Portal daje tym samym największy wybór spośród branżowych stron na polskim rynku. Rozwijamy go organicznie, serdecznie zapraszam tam również i Ciebie :)

Podobne artykuły

[wpdevart_facebook_comment curent_url="https://justjoin.it/blog/cookie-jak-zastosowac-atrybuty-domain-i-path" order_type="social" width="100%" count_of_comments="8" ]