Sesja użytkownika w PHP - zagrożenia i ochrona

Autor: Przemek Sobstel, dodano: 21-08-2009
Kategoria: Projektowanie WWW

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
 

<?php
session_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
<?php
session_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.

Ocena 2.86/5 (57.24%) (377 głosów)

Komentarze:

  • dziekuje
    Dodał: gosc data: 2012-12-12
    dziekuje serdecznie za wpis
  • Moje plany..
    Dodał: Hakier data: 2012-12-12
    Moje plany poszły na marne! :/


Dodaj komentarz:


Temat:
Twój nick:
Komentarz:
 

Prosimy o kulturę wypowiedzi. Komentarze zawierające niecenzuralne zwroty, bądź obrażające inne osoby będą usuwane. Kod HTML w wypowiedziach jest niedozwolony. Wydawca nie odpowiada za treść komentarzy.