Zarządzanie sesją w aplikacjach internetowych
Jest to uzupełnienie (tekstowe) do prezentacji dotyczącej zarządzania sesją w aplikacjach internetowych. Prezentacja jest dostępna na stronie OWASP: https://www.owasp.org/index.php/Poland, jak również tu: owasp_prezentacja.pdf.
Bezstanowy HTTP(S)
HTTP jest bezstanowy
Protokół HTTP sam w sobie jest bezstanowy. Typowa transakcja HTTP składa się z następujących kroków:
- klient nawiązuje połączenie z serwerem (TCP),
- klient wysyła żądanie do serwera,
- serwer przetwarza żądanie i zwraca odpowiedź,
- połączenie z serwerem zostaje zakończone,
Nawet w trakcie ładowania jednej strony poszczególne żądania tego samego klienta mogą pochodzić z różnych adresów IP, tak więc ani połączenie (TCP), ani adres IP klienta, nie może być wykorzystane do realizacji pewnej „ciągłości” działań użytkownika.
Optymalizacje HTTP
W ramach HTTP wprowadzono różne optymalizacje, na przykład Keep-Alive. Optymalizacja ta pozwala na wykorzystanie jednego połączenia TCP do realizacji wielu żądań. Inną optymalizacją jest HTTP pipelining. Polega ona na wysłaniu serii żądań bez oczekiwania na odpowiedź. Optymalizacje te koncentrują się przede wszystkim na wydajności. Wynika to z faktu, że zarówno operacja nawiązania połączenia TCP, jak i samo żądanie HTTP są „drogie” jeśli chodzi o wydajność. Bardzo dobrym narzędziem służącym do analizy wydajności aplikacji (strony) internetowej jest YSlow (http://developer.yahoo.com/yslow/). Optymalizacje te (Keep-Alive, pipelining) nie zmieniają jednak w żaden sposób faktu, że HTTP jest protokołem bezstanowym.
HTTPS jest (prawie) bezstanowy
Protokół HTTPS jest niczym innym, jak protokołem HTTP z dodaną warstwą SSL. Pewną zmianę wprowadza tutaj warstwa SSL oraz ID sesji SSL. ID to może być wykorzystane do realizacji „sesji” użytkownika i wiązania poszczególnych żądań klienta, jednak w praktyce możliwość ta jest bardzo rzadko wykorzystywana.
Aplikacje są stanowe
Zwykłe strony internetowe, statyczne, które nie oferują żadnej interakcji z użytkownikiem nie wymagają „stanowości”. Problem pojawia się w chwili, gdy zamiast stron pojawiają się aplikacje internetowe. W tym wypadku jak najbardziej można mówić o stanowości. Przykłady:
- Śledzenie stanu:
- użytkownik nieuwierzytelniony,
- użytkownik uwierzytelniony,
- Wiązanie kolejnych żądań użytkownika:
- operacje złożone z wielu kroków,
- zapamiętanie poprzednich kroków (i danych wprowadzonych),
- właściwa kolejność kroków,
Przykład: sklep
Aby lepiej zrozumieć stanowość aplikacji internetowej, można posłużyć się prostym przykładem sklepu internetowego. Na początku klient przegląda asortyment sklepu. Sama operacja przeglądania asortymentu nie wymaga utrzymywania informacji o akcjach użytkownika. Ideą sklepu jest jednak dokonywanie zakupów, a klient realizuje zakupy dodając do „koszyka” interesujące go przedmioty. Koszyk związany jest z klientem, w związku z czym aplikacja musi utrzymywać stan koszyka dla każdego z obecnie aktywnych klientów. W chwili, gdy w koszyku znajdują się wszystkie interesujące klienta przedmioty, idzie on „do kasy”. Dla uproszczenia zakładamy, że sklep oferuje możliwość zakupu tylko dla zarejestrowanych klientów. Użytkownik musi więc uwierzytelnić się, a aplikacja musi powiązać wcześniej istniejący koszyk, wraz z jego zawartością, z konkretnym, uwierzytelnionym już klientem. Ostatnim krokiem jest zapłata za zakupy, która może być realizowana z udziałem zewnętrznego usługodawcy. Pojawia się więc konieczność śledzenia i odnotowania przez aplikację faktu dokonania wpłaty. Po operacji zapłaty, zamówienie zostaje skierowane do realizacji. Operacja zakupu składa się z kilku prostych kroków:
- dodawanie towaru do koszyka,
- udanie się z koszykiem „do kasy”,
- zapłata za towar,
- skierowanie zamówienia do realizacji,
Kroki te muszą być zrealizowane w odpowiedniej kolejności. Można dla przykładu zastanowić się, jak zachowa się aplikacja, gdy pomiędzy krokiem „zapłata za towar” a „skierowanie zamówienia do realizacji” klient postanowi zmienić zawartość koszyka.
Sesje
Ponieważ sam HTTP nie oferuje „stanowości”, konieczne było inne rozwiązanie. Jest ono bardzo proste – kolejne żądania klienta zawierają w sobie dodatkową informację o identyfikatorze sesji, z którą są związane. Na podstawie tego parametru aplikacja działająca po stronie serwera może wiązać kolejne otrzymane żądania z istniejącymi sesjami i utrzymywać ich stan. Obecnie sesje są oferowane przez praktycznie każde „środowisko” (PHP, ASP.NET, J2EE, …) i są czymś, z czego się korzysta, a nie implementuje. W rezultacie programista często nawet nie wie, kiedy jest generowany i jak wygląda identyfikator sesji czy w jaki sposób jest przekazywany do aplikacji przez klienta.
Przekazywanie identyfikatorów sesji
Identyfikator sesji może być przekazywany na wiele sposobów. Przykładowo:
- w nagłówku HTTP (zwykle w Cookie),
- w polu typu hidden (jako parametr),
- w URL (URL Rewriting),
Przekazywanie identyfikatora w Cookie jest powszechnie stosowanym rozwiązaniem. W chwili, gdy sesja z klientem zostaje rozpoczęta, aplikacja ustawia nowe cookie, które zawiera identyfikator sesji. Przeglądarka następnie wysyła ten identyfikator z każdym kolejnym żądaniem użytkownika. Rozwiązanie z przekazywaniem identyfikatora sesji w polu ukrytym jest rzadziej spotykane, choć stosowane na przykład przez BroadVision: http://www.broadvision.com/bvsn/bvcom/ep/home.do?tabId=2&BV_SessionID=NNNN1449429114.1224350448NNNN&BV_EngineID=ccccadeejdjfiegcefecefedgfhdfng.0
Również PHP może być skonfigurowane w taki sposób, by identyfikator sesji był przekazywany w parametrze URL.
Różnica między przekazywaniem identyfikatora sesji w parametrze, a w URL jest w zasadzie niewielka. Jeśli identyfikator sesji przekazywany jest w parametrze, to w przypadku żądania POST nie jest on bezpośrednio widoczny w URL. Inaczej, gdy identyfikator ten jest przekazywany w URL, na przykład w takiej postaci: http://nix.ksc.nasa.gov/search;jsessionid=273f6jwwo9s?b=SC000402
W tym przypadku identyfikator sesji jest po prostu dopisywany do wszystkich linków (URL Rewrite) w zwracanej do klienta stronie. W rezultacie identyfikator sesji zawsze jest widoczny w URL. Dalsze rozważania, poza wątkiem o przekazywaniu identyfikatora sesji w URL, dotyczą przekazywania identyfikatora sesji w cookie.
Przekazywanie w URL jest ZŁE
Przekazywanie sesji w URL nie jest rozwiązaniem dobrym. Przede wszystkim dlatego, że znacznie ułatwia atak typu session fixation, a przynajmniej jego wariant wykorzystujący maile phishingowe lub linki do aplikacji osadzone na innych stronach. Poza ułatwieniem ataku session fixation, zwiększa również prawdopodobieństwo ujawnienia identyfikatora sesji. Adresy URL są często zapisywane na serwerach proxy lub na serwerach WWW, warto również wspomnieć o nagłówku Referer, który zawiera adres strony, z której pochodzi żądanie. W nagłówku Referer identyfikator sesji może zostać przesłany również do „obcych” serwerów, na przykład w sytuacji, gdy na stronie osadzona jest treść z obcego serwera (np. obrazek). Identyfikatory sesji można również podejrzeć w historii przeglądarki, choć akurat skuteczność tego wektora ataku jest mocno uzależniona od tego, czy użytkownik ma w zwyczaju wylogować się z aplikacji, czy tylko zamknąć okno przeglądarki. Nie bez znaczenia jest oczywiście również czas życia sesji. Z innych, bardziej nietypowych sposobów ujawnienia identyfikatorów sesji przekazywanych w URL, można wymienić screenshot lub wydruk strony.
...ale czasem konieczne
Niestety, przekazywanie parametru w URL czasami jest konieczne. Choć praktycznie wszystkie obecnie wykorzystywane przeglądarki wspierają cookies, są one często wyłączane przez użytkowników, którzy w oparciu o „fachowe artykuły w prasie” widzą w nich zagrożenie dla własnej prywatności. Warto nadmienić, że cookie sesyjne nie jest składowane przez przeglądarkę na dysku, a przynajmniej nie powinno być. Dzieje się tak, gdy cookie w trakcie ustawiania nie ma ustawionego atrybutu Expires. Takie cookie ważne jest tylko przez czas życia procesu przeglądarki. Niszczenie cookie sesyjnego wraz z zamknięciem okna przeglądarki ma dodatkowy sens. Znaczna część użytkowników wciąż ma w zwyczaju nie wylogowywać się po zakończeniu swoich akcji w aplikacji, część z nich po prostu zamyka przeglądarkę (lub tylko zakładkę, w której otwarta była strona). Jeśli cookie jest „sesyjne”, zostaje automatycznie zniszczone. Niestety, to nie działa w taki sposób, jeśli użytkownik zamyka tylko jedną zakładkę lub jedno okno (z wielu pracujących w tym samym procesie).
Gdy nie ma cookie
Ciekawą obserwacją może być sprawdzenie, jak zachowuje się aplikacja w przypadku, gdy przeglądarka nie obsługuje cookies. Część z aplikacji po prostu odmówi współpracy, bądź to „jawnie” wyświetlając użytkownikowi stosowny komunikat o konieczności obsługi cookies, bądź też niejawnie, uporczywie powracając do okna logowania. Niektóre frameworki starają się radzić sobie z taką sytuacją, w związku z czym zaobserwować można „fallback” sposobu przekazywania identyfikatora sesji do przekazywania ich w URL.
Bezpieczne Cookie – co trzeba uwzględnić
Identyfikator sesji jest często jedynym elementem, który pozwala na wiązanie poszczególnych żądań klienta. Celem atakującego może być przejęcie sesji klienta, bądź przynajmniej wykonanie w aplikacji jakichś operacji w kontekście zalogowanego użytkownika. Przejęcie cookie zawierającego identyfikator sesji w praktyce równa się przejęciu sesji, chyba, że aplikacja implementuje jakiś dodatkowy sposób weryfikacji, czy aby na pewno wszystkie żądania przychodzą od tego samego klienta. Skoro cookie z identyfikatorem sesji jest takie ważne, musi być odpowiednio chronione. Przy ochronie cookie należy uwzględnić:
- ataki cross-site scripting,
- podsłuch ruchu sieciowego,
- wysłanie cookie do innej ścieżki (aplikacji),
- wysłanie cookie do innego serwera,
Ataki XSS
Częstym przykładem potwierdzającym wykonalność ataku cross-site scripting jest wykonanie fragmentu kodu alert(document.cookie), co samo w sobie jest zupełnie nieszkodliwe. Ma to dość niespodziewane efekty uboczne, często nawet dla firm produkujących aplikacje webowe atak XSS kojarzy się z „wyskakującymi okienkami”, które właściwie nic nie robią. Zamiast wyświetlenia okienka z zawartością cookies, możliwe jest jednak na przykład ich wysłanie na zewnętrzny serwer. Błędy prowadzące do XSS, czyli brak walidacji danych wejściowych oraz brak encodingu danych wyjściowych są wciąż bardzo powszechne, więc założenie, że w aplikacji nie ma tego typu błędów, może być ryzykowne. Jak więc chronić cookie zawierające identyfikator sesji? Microsoft może nie ma najlepszej opinii w temacie bezpieczeństwa, również Internet Explorer uznawany jest za złą (niebezpieczną) przeglądarkę. Pozbywając się uprzedzeń trzeba obiektywnie stwierdzić, że strategia trustworthy computing (http://en.wikipedia.org/wiki/Trustworthy_Computing) przynosi efekty. Przejęcie identyfikatora sesji za pomocą cross-site scripting jest realnym scenariuszem ataku, więc by mu przeciwdziałać w Internet Explorer 6.0 wprowadzono dodatkową flagę jaką może być opatrzone cookie: HttpOnly. Tak oznaczone cookie ma być niedostępne z poziomu skryptów. Obecnie flaga ta jest wspierana również przez przeglądarki Firefox i Opera. Wsparcie przez inne przeglądarki tak naprawdę nie ma znaczenia, bo po pierwsze ich udział w rynku jest znikomy, a poza tym flaga ta jest jedynie elementem systemu zabezpieczeń, a nie jego głównym ogniwem. Koszt dodania flagi HttpOnly do cookie sesyjnego jest praktycznie żaden, nie ma problemów z kompatybilnością (jeśli przeglądarka nie obsługuje flagi HttpOnly, po prostu ją zignoruje), a potencjalny zysk jest duży. Warto również wspomnieć, że flagę HttpOnly można obejść z użyciem XmlHttpRequest. Wykonując z poziomu skryptu operację open, możliwe jest następnie przeczytanie zwróconej odpowiedzi. W szczególności odpowiedź może zawierać:
- odpowiedź serwera ustawiającą cookie,
- odpowiedź serwera na żądanie TRACE,
Tu co prawda część (wszystkie?) implementacje XmlHttpRequest blokują metodę TRACE, choć to znów jest możliwe do obejścia. Najlepiej więc zadbać o to, by serwer nie obsługiwał metody TRACE. Choć flaga HttpOnly nie jest w 100% skuteczna, jej stosowanie w połączeniu z cookie sesyjnym należy uznać za dobrą praktykę i po prostu stosować w myśl zasady defence-in-depth.
Podsłuch ruchu sieciowego
Najlepszą metodą zabezpieczenia się przed podsłuchem ruchu sieciowego jest użycie SSL, przy czym SSL powinien obejmować całość sesji, a nie tylko jej jeden etap, na przykład samo uwierzytelnienie. W takim przypadku atakujący co prawda nie pozna hasła użytkownika, może jednak przechwycić cookie i przejąć sesję. To, co będzie w stanie zrobić w aplikacji, zależy już tylko od jej konstrukcji. Jeśli aplikacja wykorzystuje SSL, warto oznaczyć cookie flagą Secure. Powoduje ona, że cookie to będzie wysyłane do serwera jedynie po bezpiecznym połączeniu. Czasami zdarza się, że aplikacja ma taki sam adres, jak, na przykład, strony informacyjne, a jedyną różnicą jest wykorzystanie SSL dla aplikacji. Ustawienie flagi Secure powoduje, że cookie sesyjne ustawione w aplikacji chronionej przez SSL nie zostanie przez przypadek wysłane połączeniem nieszyfrowanym.
Przekazanie Cookie do innego serwera
Cookie jest domyślnie wysyłane przez przeglądarkę do serwera, który je ustawił. Zachowanie to można zmienić modyfikując atrybut Domain cookie. W szczególności można pozwolić na przesyłanie cookie do innych serwerów w danej domenie. W połączeniu na przykład z atakami na DNS (spoofing, cache poisoning) może umożliwić to na ujawnienie cookie sesyjnego atakującemu. Jeśli dla przykładu cookie dostępne jest dla domeny owasp.org, „wystarczy” przekonać klienta, że istnieje host evil.owasp.org (cache poisoning) i skłonić go do jego odwiedzenia. Atak nie jest może trywialny, ale jak najbardziej wykonalny. Dlatego też nie należy (bez wyraźnej potrzeby) pozwalać na przesyłanie cookie do poddomen.
Przekazanie Cookie do innej ścieżki
Kolejnym istotnym atrybutem cookie jest Path. Określa on dla jakiej ścieżki na serwerze cookie zostało ustawione. Niestety, bardzo często ustawiany jest on po prostu na / (root), co oznacza, że cookie jest wysyłane do wszystkich ścieżek na serwerze. Często nie jest to rozwiązanie dobre, szczególnie gdy na jednym serwerze pracuje wiele aplikacji, na przykład:
Przy ustawieniu Path=/ cookie ustawione przez app1 będzie wysłane również do app2. Jeśli takie współdzielenie cookie nie jest potrzebne, należy ograniczyć cookie do ścieżki, w której rzeczywiście działa aplikacja.
Cookie powinno być losowe
Cookie, a właściwie identyfikator sesji, powinien być losowy. Dlaczego? Można posłużyć się przykładem ostatnich ataków na DNS, które pozwalają na skuteczny DNS spoofing i cache poisoning. Aby odpowiedź została uznana za prawidłową, muszą zgadzać się w niej określone parametry (np. Query ID). Problem w tym, że przestrzeń możliwych wartości nie jest zbyt duża, a i z losowością bywały problemy. W efekcie przeprowadzenie skutecznego ataku (na podatne serwery) jest dość proste. Jak to się przekłada na identyfikatory sesji? W tym przypadku odgadnięcie (przewidzenie) identyfikatora sesji w praktyce pozwala na przejęcie sesji. Oznacza to, że wartość identyfikatora sesji musi być ciężka do przewidzenia, co najprościej osiągnąć wykorzystując wartość losową.
Niby dobrze, a źle
Wygenerowanie prawdziwie losowej wartości może być jednak problemem. Przede wszystkim nie należy samemu pisać generatorów liczb pseudolosowych, bo można coś zepsuć (patrz: Debian i poprawka do OpenSSL). Dość dobrze w roli generatora mogą sprawdzać się funkcje typu md5, sha1, na których wejście podawane są jakieś określone dane, na przykład:
- nazwa użytkownika,
- IP klienta,
- timestamp,
Choć rezultat md5(username, IP, timestamp) może spełniać warunek „duża losowa wartość”, to rozwiązanie takie jest złe, szczególnie, jeśli algorytm generowania identyfikatora będzie znany. Wynika to z faktu, że zarówno nazwę użytkownika, jak i adres IP można określić, natomiast odgadnięcie wartości timestamp może być łatwe – w końcu wartość ta monotonicznie rośnie.
Już lepiej
Prostą poprawką do powyższego algorytmu może być dodanie funkcji HMAC: HMAC(md5(username, IP, timestamp), key) Oczywiście w tym wypadku losowy (unikalny dla każdej instancji aplikacji) musi być klucz key. Zwykle jednak wystarczy zdać się na wykorzystywane środowisko, które w większości wypadków generuje losowy identyfikator sesji. Dla pewności jednak losowość tą można sprawdzić.
Analiza losowości cookie
Do analizy losowości można wykorzystać narzędzie Burp Suite, a konkretnie jego moduł Sequencer. Co prawda WebScarab również zawiera moduł do analizy losowości identyfikatorów sesji, lecz jest on dość prymitywny i w zasadzie nadaje się tylko do śledzenia zmienności wartości identyfikatora w czasie. Przedstawione zostaną przykłady:
- „prawdziwy random”
- md5(a, b, timestamp)
- timestamp + random
"Prawdziwy random"
Jak widać według testów 110 (ze 128 bitów) jest losowych (wskazanie to oczywiście różni się od przyjętych warunków).
Przejścia między poszczególnymi wartościami na poszczególnych pozycjach w identyfikatorze są równie prawdopodobne. Wykazane zostały dwie anomalie, gdzie na dwóch pozycjach przejścia między pewnymi wartościami były bardziej prawdopodobne, niż wskazuje to statystyka.
Na każdej pozycji występują wszystkie dostępne wartości znaków (0123456789ABCDEF).
md5(a, b, timestamp)
W tym przypadku analiza również wykazuje, że dane są losowe. Losowość ta jednak znika, jeśli uwzględni się sposób generowania wartości.
timestamp + random
W tym wypadku jedynie około 55 bitów jest losowych, czyli mniej więcej połowa składających się na identyfikator.
Jedynie pierwsza połowa identyfikatora jest losowa.
Analiza częstości przejść również wykazuje sporo anomalii w drugiej części identyfikatora.
Wszystkie dostępne znaki występują tylko w pierwszej połowie identyfikatora i przy jego końcu. W znacznej części tokenu dla wszystkich 20000 próbek wartość znaków była stała.
Session Fixation, Session Adoption
Jeśli cookie jest losowe, nie można go podsłuchać i nie można go wykraść, to może uda się go ustalić wcześniej?
A ja znam Twoje ID
Session fixation polega na ustaleniu znanej wartości identyfikatora sesji. Po prostu atakujący pobiera prawidłowy identyfikator bezpośrednio z atakowanej aplikacji. Następnie sesja jest podtrzymywana, by nie wygasła, a atakujący czeka, aż ofiara zaloguje się do aplikacji wykorzystując znaną mu sesję. Atakujący oczywiście stara się skłonić ofiarę do odwiedzenia strony i zalogowania się do aplikacji, na przykład rozsyłając phishing. Scenariusz taki jest bardzo realny, można przeczytać o jego praktycznym zastosowaniu tu: Web Application Incident Response & Forensics: A Whole New Ball Game! (http://www.blackhat.com/presentations/bh-usa-06/BH-US-06-Willis.pdf). Tego typu atak jest bardzo łatwy, jeśli aplikacja akceptuje identyfikatory sesji przekazywane w URL. Oczywiście jeśli identyfikator sesji przekazywany jest w inny sposób, atak session fixation nadal może być możliwy, choć sam proces zmuszenia ofiary do wykorzystania znanego identyfikatora sesji może być trudniejszy.
A Twoje ID będzie...
Session adoption polega na możliwości „nadania” identyfikatora sesji. W przypadku zwykłego session fixation identyfikator sesji powinien być prawidłowy a sesja powinna istnieć na serwerze, gdyż w przeciwnym wypadku serwer wygenerowałby nowy identyfikator sesji. Czasami jednak serwer akceptuje identyfikator sesji przekazany przez klienta, nawet jeśli taka sesja nie istnieje. Serwer tworzy wówczas nową sesję o identyfikatorze przesłanym przez klienta.
Scenariusze ataku
Zdalny
Scenariusz ataku zdalnego w zasadzie został już przedstawiony. Atakujący tworzy na serwerze sesję, a następnie skłania swoje ofiary do zalogowania się do aplikacji, z wykorzystaniem znanego atakującemu identyfikatora sesji. W tym celu może wykorzystać phishing.
Lokalny
Atak lokalny ma szansę powodzenia tam, gdzie użytkownicy współdzielą komputery, na przykład w kafejkach internetowych lub małych firmach. W tym wypadku zastawianie pułapki polega po prostu na otwarciu interesującej aplikacji, tak, by w przeglądarce zapisało się cookie sesyjne. Atakujący następnie czeka, aż ofiara skorzysta z komputera, na którym zastawiona była pułapka i uwierzytelni się do aplikacji z wykorzystaniem znanego atakującemu identyfikatora sesji. Przejęcie tej sesji to już formalność.
Dobre praktyki
Używaj SSL
Komunikacji nie można podsłuchać, jeśli jest szyfrowana, dlatego należy stosować SSL. Prawdą jest, że SSL jest kosztowny obliczeniowo, zwłaszcza SSL-handshake, jednak w bardzo wielu zastosowaniach obecny sprzęt bez problemu obsłuży ruch SSL generowany przez klientów. SSL powinien obejmować całą sesję, a nie tylko jeden jej element, na przykład uwierzytelnienie.
Zmieniaj ID sesji
Identyfikator sesji powinien być zmieniany przy każdej zmianie stanu sesji. W szczególności zmiana powinna następować przy:
- przejściu między stanem nieuwierzytelniony i uwierzytelniony,
- przejściu między stanem uwierzytelniony i nieuwierzytelniony,
- przy przejściu z HTTP na HTTPS (wskazane ponowne uwierzytelnienie),
- przy przejściu z HTTPS na HTTP,
Zmiana sesji po uwierzytelnieniu użytkownika skutecznie przeciwdziała atakom session fixation. Nawet jeśli atakującemu uda się ustalić identyfikator sesji, z jakim klient nawiąże sesje z aplikacją, to i tak ulegnie on zmianie po operacji uwierzytelnienia, a więc atakujący nie będzie znał identyfikatora uwierzytelnionej sesji. Zmiana identyfikatora sesji przy wylogowaniu również jest wskazana. Po stronie serwera stara sesja powinna zostać zniszczona. Niektóre aplikacje mają problemy z prawidłową implementacją sytuacji wylogowania użytkownika, sesja po stronie serwera nie jest niszczona, co pozwala na wykonanie pewnych operacji w kontekście teoretycznie już wylogowanej sesji. Zakładając sytuację, w której aplikacja do części akcji wymaga SSL, warto zmieniać identyfikator sesji przy przejściu z HTTP na HTTPS i z HTTPS na HTTP. Dlaczego? W pierwszym przypadku dlatego, że identyfikator sesji HTTP przesyłany był w postaci jawnej, więc w przypadku jego przechwycenia (podsłuch ruchu) atakujący może wykorzystać go do przejścia do chronionej części aplikacji. Zabezpieczenie to ma sens właściwie tylko wtedy, gdy przy przejściu z HTTP na HTTPS konieczne jest ponowne uwierzytelnienie użytkownika. Zmiana identyfikatora sesji przy przejściu z HTTPS na HTTP z kolei pozwala na uniknięcie sytuacji, gdy atakujący „cofa się” z HTTP na HTTPS.
Czas życia sesji
Użytkownicy mają w zwyczaju nie wylogowywać się z aplikacji, dlatego każda sesja powinna kończyć się po określonym czasie bezczynności użytkownika. Problemem może być jednak dobór właściwego czasu życia sesji. Wybór czasu zbyt krótkiego może spowodować, że sesja wygaśnie, mimo że użytkownik wcale nie był bezczynny, lecz na przykład pracowicie wypełniał formularz wniosku o kredyt. Ustawienie zbyt długiego czasu sesji z kolei może ułatwić atak denial-of-service. Każda sesja zajmuje zasoby serwera, które nie są nieograniczone.
Utrudnianie życia (atakującemu oczywiście)
W ramach przeciwdziałania kradzieży sesji można zapamiętywać informacje o kliencie, który utworzył sesję. Można na przykład zapamiętywać adres IP klienta i hash wartości User-Agent. Przy każdym kolejnym żądaniu dane te mogą być weryfikowane, a w przypadku, gdy są błędne, sesja powinna być niszczona. Teoretycznie taka technika może utrudnić przejęcie sesji i można ją traktować jako jeden z elementów ochrony, ale ma też swoje słabości. Przykładowo – nie ma wcale gwarancji, że kolejne żądania tego samego klienta przyjadą do serwera z tego samego adresu IP. Tu z kolei można próbować stosować logikę rozmytą. Jeśli na przykład zmianie uległ zarówno adres IP, jak i User-Agent, można uznać, że sesja jest przejęta i ją zniszczyć. Jeśli z kolei zmianie uległ wyłącznie adres IP można skorzystać z geolokacji i na przykład założyć, że nagła zmiana kraju przez klienta w trakcie korzystania z aplikacji jest mało prawdopodobna, natomiast przemieszczanie się w tym samym rejonie geograficznym może się zdarzyć.
Czyść sesję
W niektórych przypadkach wykorzystywane są dwa identyfikatory. Pierwszy z nich to identyfikator sesji, drugi – identyfikator uwierzytelnienia ustawiany po pomyślnym uwierzytelnieniu. W tym przypadku zwykle posiadanie samego identyfikatora sesji nie wystarczy do działania w jej kontekście. Ciekawym jednak eksperymentem może być zalogowanie się na innego użytkownika z wykorzystaniem tego samego identyfikatora sesji. Aplikacja może nie przewidywać takiej sytuacji i dane później logującego się użytkownika nadpiszą dane użytkownika już uwierzytelnionego, w związku z czym wcześniej uwierzytelniony użytkownik może nagle stać się kimś innym, na przykład administratorem w aplikacji. Może się też zdarzyć tak, że nowy użytkownik korzystający z wcześniej wykorzystanej sesji, może otrzymać „coś w spadku” po wcześniejszym użytkowniku. Najlepszym rozwiązaniem jest niszczenie sesji po jej zakończeniu, jeśli to z jakichś powodów nie jest możliwe, sesja powinna być czyszczona, zarówno przy jej zakończeniu(wylogowaniu użytkownika), jak i rozpoczęciu. Dlaczego czyścić sesję przy jej rozpoczynaniu? Dlatego, że sesja wcale nie koniecznie musiała zostać prawidłowo wylogowana wcześniej, a więc może zawierać informacje o poprzednim użytkowniku.
Cross Site Request Forgery
Celem atakującego nie musi być przejęcie sesji klienta. Czasami wystarczy, gdy w jego kontekście wykona się jakaś akcja. Akcje są odpowiedzią na żądania klienta, więc jedyne co musi zrobić atakujący, to przekonać klienta, by wysłał odpowiednie żądanie.
Wysyłanie Cookie
Pewną słabością cookies jest fakt, iż są one wysyłane automatycznie przez przeglądarkę, do serwera, o ile cookie istnieje, oraz:
- zgadza się ścieżka,
- zgadza się host/domena,
- połączenie jest szyfrowane (jeśli flaga Secure),
Cookie sesyjne nie są związane z konkretnym oknem lub zakładką w przeglądarce, lecz są współdzielone przez wszystkie zakładki lub okna pracujące w tym samym procesie. W efekcie jeśli w jednym oknie (zakładce) użytkownik jest zalogowany do aplikacji i otworzy w zupełnie innej zakładce stronę, która spowoduje wykonanie żądania do aplikacji, to przeglądarka wyśle wraz z nim cookie sesyjne zalogowanego użytkownika.
Przykład: img src=
Jeden z najbardziej trywialnych przykładów na wygenerowanie żądania to osadzenie na stronie „obrazka”, gdzie jego źródłem jest link do akcji, która ma zostać wywołana. Oczywiście w tym przypadku wszystkie dane muszą być przekazane w GET. Jeśli użytkownik jest zalogowany do serwisu, to stosowne cookie sesyjne zostanie wysłane i nie pomoże:
flaga HttpOnly – dostęp do cookie wcale nie chce uzyskać skrypt,
- flaga Secure – cookie zostanie wysłane połączeniem szyfrowanym,
- atakujący nie musi zgadywać cookie,
To, że w tym przykładzie wykorzystana została metoda GET nie oznacza wcale, że wysłanie danych POST rozwiązuje problem. Zamiast „obrazka” można osadzić skrypt, który po prostu wyśle odpowiedni formularz do aplikacji. Żadna z „dobrych praktyk” wymienionych przy zarządzaniu sesją nie powstrzyma ataku Cross-Site Request Forgery. Nie oznacza to, że praktyki te są zbędne, po prostu one nie mają na celu zabezpieczenie przed tego typu atakiem.
Jak się bronić przed Cross Site Request Forgery
Może się wydawać, że sprawdzanie nagłówka Referer w wypadku CSRF rozwiąże problem. Rzeczywiście, rozwiązanie to jest czasami skuteczne, problem w tym, że nagłówek Referer może być sfałszowany. Atak CSRF opiera się na fakcie, że atakujący dokładnie wie, jakie żądanie musi wysłać. Rozwiązania chroniące przed CSRF, na przykład CSRFGuard: http://www.owasp.org/index.php/CSRF_Guard) opierają swoje działanie na wymaganiu w żądaniu dodatkowej wartości, której atakujący nie może odgadnąć ani nie może poznać. Niektóre frameworki aplikacyjne same z siebie umieszczają tego typu dane, na przykład ViewState (ASP.NET, JSF), czy org.apache.struts.taglib.html.TOKEN w Struts.