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.
Spis treści
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 ustawieniadomain
. Oznacza to, że będzie znajdowało się tylko w żądaniach do domenymail.example.com
. mailsession1
imailsession2
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
imailsession4
ustawiane są dla domeny macierzystej najwyższego poziomu, przez co zostaną zignorowane.mailsession5
imailsession6
ustawiane są dla zupełnie innej domeny i one też zostaną zignorowane.mailsession7
imailsession8
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
imailsessiona
są ustawiane dla subdomeny aktualnej domeny. Jest to niepoprawne ustawienie i zostaną zignorowane.mailsessionb
,mailsessionc
,mailsessiond
imailsessione
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
iglobal_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.