Mechanizmu sesji używamy do identyfikacji i śledzenia użytkownika pomiędzy kolejnymi żądaniami. W rezultacie nie musi on podawać loginu i hasła przed obejrzeniem każdej podstrony w ramach danej witryny, a my możemy go rozpoznać, przydzielić odpowiedni zestaw uprawnień oraz odczytać specyficzne dla niego dane (np. język, adres e-mail, itp.).
W tym celu wykorzystujemy specjalny identyfikator – przekazywany w adresie URL lub przez cookies. Problem z punktu widzenia bezpieczeństwa wynika stąd, że użycie przez atakującego tego samego identyfikatora, co prawowity użytkownik może prowadzić do nieuprawnionego dostępu do aplikacji i wykonania operacji w jego imieniu. Niestety wbudowany mechanizm PHP pozostawia w tym względzie wiele do życzenia, co przyznaje nawet manual, dlatego to na programiście ciąży odpowiednie zabezpiecze aplikacji.
Można wyróżnić dwie podstawowe grupy ataków związanych z sesjami: przechwycenie sesji (ang. Session Hijacking) oraz wymuszenie sesji (ang. Session Fixation).
Session Hijacking
Session Hijacking odnosi się do tych wszystkich ataków, gdzie kraker próbuje uzyskać dostęp do istniejącej sesji użytkownika, tj. gdy identyfikator został przydzielony już wcześniej.
Sposób przekazywania identyfikatora sesji
Identyfikator sesji możemy przekazywać zarówno w adresie URL, jak i w ciasteczku. Pierwszy z wymienionych sposobów zdecydowanie nie jest dobrym wyborem, ponieważ :
- Każda osoba używająca tego samego komputera będzie mogła poznać identyfikator sesji z historii stron przeglądarki.
- Adresy URL często zapisywane są przez serwery proxy, dzienniki zdarzeń, itp. W wyniku ich analizy można przeczytać identyfikatory sesji.
- Bardzo łatwo zmodyfikować identyfikator sesji poprzez manipulacje łańcuchem żądania.
- Kiedy użytkownik przechodzi na inną stronę, adres URL wraz z identyfikatorem sesji dostępny jest w nagłówku HTTP Referer.
Użytkownik, przekazując adres URL innym, nieumyślnie udostępnia także identyfikator sesji.
Naturalnym wyborem do przekazywania identyfikatora sesji są więc ciasteczka. Wadą tego rozwiązania jest możliwość odmówienia przyjęcia cookies przez odwiedzających witrynę. Niestety jest to koszt, który trzeba ponieść, ale chyba lepszym wyjściem jest wyjaśnienie użytkownikowi dlaczego ciasteczka są potrzebne, niż opieranie obsługi sesji na mechaniźmie mało bezpiecznym.
Aby wymusić używanie ciasteczek do przekazywania identyfikatora sesji powinniśmy ustawić odpowiednie dyrektywy konfiguracyjne :
session.use_cookies – ustawia przekazywanie identyfikatora w ciasteczkach,
session.use_only_cookies – ustawia pobieranie identyfkatora tylko z ciasteczek,
session.trans_sid – ustawia przekazywanie identyfikatora poprzez adres URL i formularze WWW– możliwość tą oczywiście należy wyłączyć.
W konfiguracji powinniśmy też ustawić odpowiednie parametry dotyczące samego ciasteczka sesji. Szczególną uwagę należy zwrócić na ograniczenie ścieżki, dla której powinno ono obowiązywać, ponieważ domyślne ustawienie dla całego głównego katalogu „/” zwiększa polę do manewru dla atakującego.
Cross Site Scripting (XSS)
Nie ma chyba programisty, który nie słyszałby o tym ataku. W skrócie polega on na wstrzykiwaniu złośliwego kodu w oryginalną treść strony, przeważnie poprzez wszelkiego rodzaju formularze internetowe, gdzie zatwierdzona treść jest potem wyświetlana. XSS może być także wykorzystany do wykradnęcia ciasteczka zawierającego identyfikator sesji :
<script>alert(document.cookie());</script>
Jak się zabezpieczyć? Z pomocą przychodzi tutaj flaga cookie httpOnly. Jej zadaniem jest instruowanie przeglądarki, aby nie udzielała dostępu do obiektu document.cookie jakimkolwiek językom skryptowym działającym po stronie klienta, np. JavaScriptowi. Począwszy od PHP 5.2.0, aby ją włączyć domyślnie dla wszystkich ciasteczek sesyjnych wysyłanych przez aplikację, wystarczy ustawić dyrektywę session.cookie_httponly. Rozwiązane to nie jest jednak pozbawione wad. Po pierwsze należy mieć świadomość, że nie wszystkie przeglądarki wspierają httpOnly – dotyczy to zwłaszcza starszych wersji. Po drugie cookie wciąż może być odczytane używając metody TRACE (tzw. Cross Site Tracing):
<script>var xh = new ActiveXObject(“Microsoft.XMLHTTP”);xh.open("TRACE", "http://www.example.com", false);xh.send();alert(xh.responseText);</script>
O ile w pierwszym przypadku jedyne co nam pozostaje to namawianie użytkowników do aktualizacji używanych przeglądarek, o tyle w drugim możemy się zabezpieczyć uniemożliwiając wykonanie metody TRACE na poziomie serwera np.
RewriteEngine On
RewriteCond %{REQUEST_METHOD} ^TRACE
RewriteRule .* – [F]
Pamiętajmy jednak, że w przypadku ataków typu XSS, w pierwszej kolejności powinniśmy zawsze dogłębnie filtrować wszystkie dane wejściowych, tak aby niemożliwe było wstrzyknięcie obcych skryptów.
Sposób przechowywania danych sesyjnych
Rozszerzenie PHP Session domyślnie przechowuje dane sesyjne w plikach na serwerze lokalnym w jednym katalogu (/tmp). Na serwerach współdzielonych, prawa odczytu i zapisu do tego katalogu mają wszyscy użytkownicy i w praktyce mogą uzyskać dostęp do wszystkich identyfikatorów oraz danych sesji. Co więcej, atakujący może to wykorzystać do podmiany pliku sesji lub danych w nim zawartych. Aby zabezpieczyć się przed tym zagrożeniem, najlepiej skorzystać z dyrektywy konfiguracyjnej session.save_path, która wskazuje nowy katalog do przechowywania plików sesji, lub zdefiniować własne funkcje do obsługi magazynu przechowywania danych sesyjnych, np. do zapisu w bazie danych lub pamięci współdzielonej.
Funkcje te rejestrujemy za pomocą session_set_save_handler(). Dodatkowo, w systemach, w których wymagany jest wysoki poziom bezpieczeńśtwa, dane sesyjne możemy zabezpieczyć poprzez szyfrowanie bezpośrednio przed umieszczeniem w magazynie danych, chociażby używając rozszerzenia Mcrypt.
Zarządzanie czasem trwania sesji
Sesje, które nie wygasją w odpowiednio krótkim odstępie czasu, dają atakującemu o wiele więcej możliwości na atak. Mechanizm obsługi sesji powinna w pełni kontrolować czas, po jakim sesja traci ważność albo jest usuwana z magazynu przechowującego. Sesja powinna być przerwana, gdy nie stwierdzono żadnej aktywności przez pewien okres czasu, np. 30 minut, lub gdy nastąpi błąd bezpieczeństwa. Użytkownik powinien mieć także możliwość przerwania sesji samemu, poprzez opcje wylogowania z systemu. Mechanizm powinień automatycznie usuwać z magazynu danych te identyfikatory i dane sesyjne, których czas ważności został przekroczony.
Także ciasteczka powinny mieć ograniczony czas istnienia. Oczyszczanie oferowane przez rozszerzenie Session nie jest doskonałe, ponieważ odbywa się ono z zadanym prawdopodobieństwem, więc możliwe jest, że sesja nie zostanie usunięta, mimo że jej czas aktywności minął.
Ustawienie rawdopodobieństwa na 100% mogłoby się sprawdzić w przypadku bardzo małych serwisów o znikomej oglądalności – w innym wypadku byłoby zbyt obciążające. Alternatywnie do usuwania przestarzałych sesji, można wykorzystać demon Cron lub informacje na temat wygaśnięcia przetrzymywać bezpośrednio wśród danych sesyjnych.
Usuwanie sesji zawsze po określonym czasie
<?phpsession_start();$expiryTime = 1800;if (!isset($_SESSION['start_time'])){ $_SESSION['start_time'] = time();}if ((int)$_SESSION['start_time'] + $expiryTime < time()){ echo 'sesja wygasła!'; $_SESSION = array(); if (isset($_COOKIE[session_name()])) { setcookie(session_name(), '', time()-3600, '/'); } session_destroy();}?>
Podsłuchiwanie ruchu sieciowego
W ruchu sieciowym standardowo wszystkie dane wysyłane są tekstem jawnym. Każdy, komu uda się podsłuchać ten ruch, z łatwością może przechwycić identyfikator sesji i wykorzystać go. Jedynym rozwiązaniem jest wykorzystanie bezpiecznego protokołu HTTPS. Wtedy komunikacja pomiędzy klientem i serwerem jest w pełni szyfrowana. Wdrożenie SSL nie zawsze jest jednak możliwe, zwłaszcza w przypadku nisko-budżetowych przedsięwzięć. Na szczęście stosując inne techniki opisane w tym artykule możemy zapewnić wystarczająco wysoki poziom bezpieczeństwa.
Siła identyfikatora
Identyfikator sesji powinien być opdowiednio długi i losowy, trudny do odgadnięcia oraz trudny do odtworzenia. Wbudowany mechanizm obsługi sesji prezentuje się pod tym względem dość dobrze – do wyboru daje funkcje skrótu MD5 i SHA-1 o różnej długości – dyrektywy session.hash_function oraz session.hash_bits_per_character. Dodatkowo możemy spowodować zmianę w działaniu generatora liczb losowych poprzez użycie dyrektyw session.entropy_file oraz session.entropy_length. Wskazują one plik i długość ciągu w tym pliku, który ma być użyty do wygenerowania identyfikatora, np. może to być /dev/random, który w systemach Linux zwraca losowe wartości. Jednakże dla początkujących programistów lub po prostu niebyt pewnie się czujących w temacie, radziłbym co najwyżej zmianę algorytmu na SHA-1, w przeciwnym razie wszelkie zmiany konfiguracji mogą mieć całkiem odwrotny efekt, czyli obniżenie stopnia losowości generowanych identyfikatorów.
Session Fixation
Kolejny rodzaj ataku, określany terminem Session Fixation, różni się od opisanych wyżej metod tym, że atakujący nie skupia swojej uwagi na zdobyciu identyfikatora sesji ofiary, a raczej na skłonieniu ofiary do użycia identyfikatora określonego przez niego samego, np. poprzez odpowiednio spreparowany odnośnik z dołączonym identyfikatorem sesji. Innymi słowy, użytkownik zostaje nieświadomie skłoniony do użycia sesji zainicjowanej przez atakującego.
Atak ten przebiega standardowo w trzech krokach :
Ustanowienie sesji – w pierwszym kroku atakujący tworzy losowy identyfikator sesji lub rozpoczyna nową sesję na docelowym serwerze i pozyskuje z niej identyfikator. Zazwyczaj musi on utrzymywać sesję przez wielokrotne wysyłanie żądań, aby uniknąć jej wygaśnięcia.
Wymuszenie sesji – atakujący przekazuje ofierze ustalony identyfikator. Przekazanie to może się odbyć na wiele sposobów m.in. może on wykorzystać techniki polegające na iniekcji i wykonaniu wrogiego kodu, np. Cross Site Scripting; może także po prostu skłonić ofiarę do uruchomienia odpowiednio spreparowanego odnośnika lub formularza HTML.
Uzyskanie dostępu – w ostatnim kroku atakującemu pozostaje tylko czekać, aż ofiara zaloguje się do aplikacji używając ustalonego identyfikatora, po czym wykorzystując ten sam identyfikator atakujący może podszyć się pod ofiarę.
Tego rodzaju zagrożeniu możemy przeciwdziałać stosunkowo łatwo. Po pierwsze, przy każdej zmianie poziomu uprawnień użytkownika powinniśmy wygenerować nowy identyfikator sesji za pomocą funkcji session_regenerate_id(true). Należy pamiętać o użyciu argumentu true, w przeciwnym razie poprzedni identyfikator, wraz z towarzyszącymi mu danymi, nie zostanie usunięty z systemu. Absolutnym minimum jest, aby ta zmiana nastąpiła po przejściu procesu uwierzytelniania. Oczywiście zawsze możemy regenerować identyfikator przy każdym żądaniu, jednakże wydaje mi się, że może mieć to zbyt duże konsekwencje wydajnościowe. Wszystko zależy od wielkości i rodzaju systemu informatycznego. Pośrednim rozwiązaniem może być podmiana identyfikatora co kilka żądań.
Kolejnym środkiem zaradczym jest uniemożliwienie tzw. adopcji sesji (ang. Session Adoption), czyli ignorowanie identyfikatorów pochodzących od użytkownika, które nie znajdują się w magazynie danych – akceptowalne są tylko te, które system sam utworzył. Patrz Listing 2.
Przeciwdziałanie adopcji sesji<?phpsession_start();if (!isset($_SESSION['our_own'])){ session_regenerate_id(); $_SESSION['our_own'] = true;}?>
Pozorne zabezpieczenia
Możemy wprowadzić także dodatkowe zabezpieczenia polegające na sprawdzaniu czy żądanie pochodzi z tego samego adresu IP, przeglądarki czy też zostało wysłane z określonego adresu URL. W praktyce jednak żadne z tych rozwiązań nie jest doskonałe. Jest to wynikiem tego, że podczas przeglądania internetu kolejne żądania w ramach jednej i tej samej sesji mogą przechodzić przez różne serwery proxy – dla serwera wygląda to tak, jakby pochodziły z różnych adresów IP. To samo dotyczy ciągu określającego przeglądarkę. Fakt ten znacznie zmniejsza funkcjonalność całej aplikacji, ponieważ w praktyce ogranicza dostęp użytkowników do systemu, co przeważnie jest nie do zaakceptowania.
Podsumowanie
Jak pokazują wyżej omówione zagrożenia związane z poprawnym uwierzytelnianiem i identyfikacją użytkownika, kwestia ochrony tych mechanizmów nie powinna być lekceważona, tym bardziej że standardowy mechanizm sesji zaimplementowany w PHP nie daje wystarczającego poziomu bezpieczeństwa.
Należy zdać sobie sprawę, że nie jest możliwa stuprocentowa ochrona przed przechwyceniem identyfikatora sesji, dlatego krytyczne operacje dla działania systemu oraz konta użytkownika, jak np. zmiana hasła, powinny być poprzedzone ponownym uwierzytelnianiem. W skócie staraj się ograniczyć straty, jakie mogłyby wyniknąć w wyniku przejęcia sesji przez krakera.
