Przejdź do treści

Zarządzanie sesją

Sesja użytkownika tworzona jest po poprawnym zalogowaniu. Pozwala aplikacji rozpoznawać użytkownika, dzięki czemu nie musi on podawać hasła przy każdym otwarciu strony ani przy każdej operacji.

Każde otwarcie strony i każdy przesłany formularz to osobne żądanie HTTP. Protokół nie łączy ich z wcześniejszym logowaniem. Dlatego po zalogowaniu aplikacja tworzy sesję i przekazuje przeglądarce jej identyfikator. Od tej chwili przeglądarka dołącza go do kolejnych żądań, a aplikacja wiąże je z kontem użytkownika.

Przez cały czas trwania sesji identyfikator pełni funkcję tymczasowego hasła. Kto go przejmie, może działać jako zalogowany użytkownik. Bezpieczeństwo zależy więc od tego, jak identyfikator jest tworzony, przesyłany, przechowywany i unieważniany.

Dwa sposoby utrzymywania sesji

Zwykle serwer przechowuje stan sesji, a identyfikator w ciasteczku jest wyłącznie losowym odnośnikiem do niego. Usunięcie wpisu po stronie serwera natychmiast kończy sesję.

W modelu bezstanowym, na przykład opartym na JWT, serwer sprawdza zawartość i podpis tokenu. Nie przechowuje wpisu sesji, który można po prostu usunąć, dlatego token pozostaje ważny do daty wygaśnięcia. Nie zmienia tego wylogowanie, zmiana hasła ani zablokowanie konta. Natychmiastowe odebranie dostępu wymaga osobnego mechanizmu, opisanego w dalszej części artykułu.

Skutki udanego ataku

Skutki niepoprawnego zarządzania sesją

Błędy w zarządzaniu sesją mogą prowadzić do:

  • przejęcia konta bez znajomości hasła i bez dostępu do drugiego składnika uwierzytelniania,
  • odczytu, zmiany, usunięcia lub eksportu danych dostępnych dla przejętego konta,
  • uzyskania kontroli nad aplikacją, jeżeli przejęte konto ma uprawnienia administracyjne,
  • utrzymania dostępu przez atakującego mimo wylogowania użytkownika lub zmiany hasła,
  • braku możliwości natychmiastowego zakończenia sesji podczas incydentu,
  • trudności w ustaleniu, kto i kiedy korzystał z konta.

Typowe problemy

Identyfikator sesji w adresie URL

Przekazywanie identyfikatora w adresie, na przykład jako parametr ?sessionid=..., pozbawia go poufności. Adresy URL są zapisywane w logach serwerów i urządzeń pośredniczących. Zależnie od polityki Referrer-Policy mogą również zostać wysłane w nagłówku Referer.

Adres z paska przeglądarki bywa kopiowany i przesyłany współpracownikom, wklejany do zgłoszeń w helpdesku lub widoczny na publikowanych zrzutach ekranu. Jeżeli zawiera identyfikator sesji, użytkownik nieświadomie przekazuje w ten sposób dostęp do swojego konta.

Słabe lub przewidywalne identyfikatory

Jeżeli aplikacja generuje identyfikatory sesji w sposób schematyczny, atakujący może przewidzieć ich wartości i przejąć konta zalogowanych użytkowników. Dzieje się tak, gdy identyfikator powstaje z kolejnego numeru, znacznika czasu albo danych konta. Obliczenie skrótu z przewidywalnych danych nie rozwiązuje problemu, ponieważ atakujący nadal może sprawdzać ich kolejne warianty. Podobnym błędem jest użycie zwykłego generatora pseudolosowego zamiast kryptograficznego oraz identyfikatorów UUID w wersji 1, które zawierają czas utworzenia i mogą zawierać adres sprzętowy karty sieciowej.

Drugim problemem jest zbyt krótki identyfikator. Nawet w pełni losowa wartość mieści się w zasięgu ataku brute force, jeżeli możliwych kombinacji jest niewiele.

Ciasteczko przesyłane bez szyfrowania

Bez atrybutu Secure przeglądarka dołącza ciasteczko również do żądań wysyłanych nieszyfrowanym protokołem HTTP. Wystarczy otworzyć stronę w tej samej domenie przez HTTP, aby identyfikator został przesłany jawnie. Atakujący kontrolujący sieć może go wtedy przechwycić.

Dodatkową warstwą ochrony jest nagłówek HSTS, który nakazuje przeglądarce łączyć się z domeną wyłącznie przez HTTPS. Opisano go w artykule Konfiguracja TLS.

Ciasteczko dostępne dla skryptów strony

Bez atrybutu HttpOnly wartość ciasteczka może odczytać skrypt działający na stronie. Jedna podatność XSS albo przejęta biblioteka zewnętrzna wystarczy wtedy, aby atakujący mógł pozyskać identyfikator sesji.

HttpOnly nie usuwa skutków XSS

Atrybut blokuje odczyt ciasteczka przez JavaScript, ale nie zatrzymuje złośliwego skryptu. Przeglądarka nadal automatycznie dołącza ciasteczko do żądań. Skrypt może więc wykonywać operacje jako użytkownik, na przykład zmienić przypisany do konta adres e-mail i przejąć konto tą drogą.

Zbyt szeroki zasięg ciasteczka

Za pomocą atrybutu Domain można rozszerzyć zasięg ciasteczka na domenę nadrzędną. Obejmuje ono wtedy również wszystkie jej subdomeny, więc przejęcie nawet mało istotnego serwisu może narazić sesję w głównej aplikacji. Bez tego atrybutu ciasteczko domyślnie obowiązuje tylko dla hosta, który je ustawił, i w większości przypadków tak powinno pozostać.

Atrybut SameSite ogranicza wysyłanie ciasteczka w żądaniach inicjowanych przez inne witryny. Zmniejsza ryzyko CSRF, ale nie obejmuje każdego scenariusza i nie powinien być jedyną ochroną operacji zmieniających dane.

Wylogowanie tylko po stronie przeglądarki

Jeżeli wylogowanie jedynie usuwa ciasteczko z przeglądarki, sesja po stronie serwera nadal istnieje. Kopia identyfikatora zdobyta wcześniej przez atakującego pozostaje ważna. Użytkownik widzi ekran logowania i uznaje, że dostęp został zamknięty, choć atakujący nadal korzysta z konta.

Przy tokenach bezstanowych nie można po prostu usunąć rekordu sesji z bazy. Serwer nie prowadzi listy aktywnych sesji, więc token pozostaje ważny do daty wygaśnięcia zapisanej w jego treści, niezależnie od tego, co użytkownik zrobi w przeglądarce.

Zbyt długo ważny token dostępowy

Token przejęty przez atakującego działa aż do daty wygaśnięcia zapisanej w jego treści. Serwer nie prowadzi listy aktywnych sesji, więc do tego momentu przyjmie każdy token z poprawnym podpisem, niezależnie od tego, czy użytkownik się wylogował, zmienił hasło albo czy konto zostało w międzyczasie zablokowane.

Czas ważności jest w tym modelu jedynym ograniczeniem dostępu, dlatego token dostępowy powinien być ważny krótko. Dłuższą pracę bez ponownego logowania zapewnia wtedy osobny token odświeżający, kontrolowany po stronie serwera i możliwy do unieważnienia w dowolnej chwili.

Brak unieważnienia sesji po zmianie poświadczeń

Zmiana hasła jest częstą reakcją użytkownika na podejrzenie przejęcia konta. Jeżeli aplikacja aktualizuje tylko skrót hasła w bazie i nie kończy pozostałych sesji, atakujący zachowuje dostęp. Użytkownik pozostaje natomiast w przekonaniu, że odzyskał kontrolę nad kontem.

Ten sam skutek ma brak unieważnienia sesji przy odzyskiwaniu dostępu do konta, zmianie adresu e-mail lub numeru telefonu służącego do odzyskiwania, dodaniu albo usunięciu składnika uwierzytelniania oraz przy blokadzie konta. Łatwo pominąć przy tym tokeny odświeżające i sesje w aplikacjach mobilnych, które działają dalej niezależnie od przeglądarki.

Unieważnienie sesji nie kończy reakcji na incydent

Atakujący mógł zapewnić sobie inny sposób dostępu do konta: dodać własny składnik uwierzytelniania, utworzyć klucz lub token API, włączyć przekazywanie poczty albo założyć dodatkowe konto. Po przejęciu konta należy sprawdzić również te elementy. Sama zmiana hasła i zakończenie sesji ich nie usuwają.

Brak wglądu w aktywne sesje

Jeżeli użytkownik nie widzi, gdzie i od kiedy jest zalogowany, przejęcie konta może długo pozostać niezauważone. Aplikacja powinna udostępniać listę aktywnych sesji z czasem rozpoczęcia, czasem ostatniej aktywności, adresem sieciowym i rozpoznanym urządzeniem, a także możliwość zakończenia wskazanej sesji lub wszystkich pozostałych jednym działaniem. Powiadomienie o zalogowaniu z nowego urządzenia może być pierwszym sygnałem incydentu.

Te same dane są przydatne po stronie systemu. Pozwalają wykrywać równoległe korzystanie z konta z odległych lokalizacji, gwałtowną zmianę urządzenia w trakcie sesji czy aktywność o nietypowej porze.

Pomieszanie sesji użytkowników

Dane zalogowanego użytkownika muszą być związane z jego sesją, a nie z aplikacją jako całością. Jeżeli trafiają do zmiennej globalnej, pola statycznego albo komponentu współdzielonego przez wszystkie żądania, przy równoczesnym ruchu jeden użytkownik zobaczy dane drugiego, a wykonane przez niego operacje zapiszą się na cudzym koncie.

Ten sam skutek daje buforowanie odpowiedzi zawierających dane zalogowanego użytkownika. Serwer proxy lub CDN zapamiętuje wtedy stronę wygenerowaną dla jednej osoby i pokazuje ją kolejnym. Błędy tego rodzaju ujawniają się zwykle dopiero przy większym ruchu, a pojedyncze zgłoszenia użytkowników bywają zbywane jako pomyłka po ich stronie.

Ograniczenie do jednej sesji

Jednoczesne korzystanie z konta na komputerze, telefonie i tablecie jest dziś normą. Ograniczenie do jednej aktywnej sesji nie zwiększa bezpieczeństwa, a jedynie zmusza użytkownika do ponownego logowania przy każdej zmianie urządzenia.

Niepoprawne użycie JWT

Podpis tokenu JWT chroni jego integralność, ale nie zapewnia poufności. Każdy posiadacz tokenu może odczytać jego treść. Jeżeli aplikacja nie weryfikuje podpisu we wszystkich punktach wejścia, atakujący może podmienić identyfikator konta lub przypisaną rolę i działać jako dowolny użytkownik, łącznie z administratorem.

Niedopuszczalne jest zaufanie do wartości pola alg w nagłówku tokenu. Aplikacja może wtedy przyjąć token bez podpisu (z wartością alg: none) albo taki, w którym algorytm asymetryczny zamieniono na symetryczny. W drugim przypadku aplikacja weryfikuje podpis kluczem publicznym, traktując go jak klucz symetryczny. Ponieważ klucz publiczny jest powszechnie znany, atakujący może użyć go również do podpisania własnego tokenu.

Poprawny podpis nie wystarcza, jeżeli aplikacja nie sprawdza pozostałych pól. Bez weryfikacji daty wygaśnięcia token działa bezterminowo, a bez sprawdzenia wystawcy i odbiorcy może zostać przyjęty token wystawiony dla innej usługi.

Osobnym problemem jest klucz podpisujący. Przy algorytmie symetrycznym krótki lub słownikowy klucz może zostać odgadnięty na podstawie przechwyconego tokenu. Atakujący może wtedy wystawiać własne tokeny. W treści tokenu mogą też znaleźć się dane, które nie powinny trafić do użytkownika.

Czas trwania sesji

Skutki zbyt długiej sesji wydają się oczywiste: im dłużej pozostaje ważna, tym więcej czasu na jej wykorzystanie ma atakujący. Aplikacja z zalogowanym użytkownikiem bywa pozostawiona otwarta na niepilnowanym komputerze, urządzenie może zostać zgubione lub skradzione, a złośliwe oprogramowanie potrafi wykraść ciasteczko sesyjne.

Zbyt krótki czas trwania sesji jest natomiast uciążliwy dla użytkowników i wywołuje u nich zachowania, które osłabiają bezpieczeństwo. Konieczność częstego logowania sprzyja wybieraniu słabych haseł, które można łatwo i szybko wpisać, oraz zwiększa niechęć do uwierzytelniania wieloskładnikowego.

Częste wyświetlanie formularza logowania osłabia również czujność. Użytkownik przyzwyczajony do wielokrotnego logowania wykonuje tę czynność odruchowo i rzadziej sprawdza, czy znajduje się na właściwej stronie. Ułatwia to wykorzystanie fałszywego formularza w ataku phishingowym. Podobny problem dotyczy próśb o potwierdzenie logowania: jeżeli pojawiają się zbyt często, użytkownik może akceptować je bez sprawdzenia.

Nie jest możliwe ustalenie czasu trwania sesji, który zapewniałby bezpieczeństwo. Zamiast tego sesję należy unieważniać, gdy pojawi się podejrzenie jej przejęcia. Przed operacjami wrażliwymi, takimi jak zmiana hasła, dodanie nowego składnika uwierzytelniania, wypłata środków czy eksport danych, należy wymagać ponownego uwierzytelnienia. Sesje długo nieaktywne warto kończyć, aby lista sesji pozostała czytelna.

Rekomendacje

Identyfikator sesji

  • generować identyfikator kryptograficznie bezpiecznym generatorem, z co najmniej 128 bitami losowości,
  • nie wyprowadzać go z numeru kolejnego, czasu ani danych konta,
  • przekazywać go wyłącznie w ciasteczku, nigdy w adresie URL,
  • nie zapisywać wartości identyfikatora w logach aplikacji, systemach monitoringu ani komunikatach błędów.

Ciasteczko sesyjne

  • ustawiać atrybuty Secure i HttpOnly,
  • dobrać wartość SameSite do sposobu logowania i traktować ją jako jedną z warstw ochrony przed CSRF,
  • nie ustawiać atrybutu Domain bez wyraźnej potrzeby, a tam, gdzie to możliwe, stosować prefiks __Host-:

    Set-Cookie: __Host-sessionid=<losowa-wartość>; Path=/; Secure; HttpOnly; SameSite=Lax
    
  • włączyć HSTS, aby przeglądarka nie wysyłała żądań przez HTTP.

Cykl życia sesji

  • kończyć sesję po stronie serwera przy wylogowaniu, a nie tylko usuwać ciasteczko w przeglądarce,
  • sprawdzać ważność sesji po stronie serwera – data ważności ciasteczka jest tylko wskazówką dla przeglądarki,
  • unieważniać sesję, gdy pojawi się podejrzenie jej przejęcia,
  • wymagać ponownego uwierzytelnienia przed operacjami wrażliwymi, takimi jak zmiana hasła, modyfikacja składników uwierzytelniania, wypłata środków czy eksport danych,
  • oferować możliwość unieważnienia wszystkich pozostałych sesji przy zmianie hasła, odzyskiwaniu konta, zmianie danych kontaktowych, modyfikacji składników uwierzytelniania oraz przy blokadzie konta,
  • nie polegać na sztywnym limicie czasu trwania sesji,
  • kończyć sesje po długim okresie nieaktywności,
  • nie ograniczać liczby równoległych sesji, jeżeli nie wymaga tego model bezpieczeństwa systemu lub warunki licencji.

Tokeny bezstanowe

  • weryfikować podpis każdego tokenu we wszystkich punktach wejścia aplikacji,
  • wymuszać oczekiwany algorytm podpisu zamiast przyjmować ten wskazany w nagłówku tokenu, w szczególności odrzucać wartość none,
  • sprawdzać datę wygaśnięcia oraz pola wystawcy i odbiorcy tokenu,
  • dobierać klucze do algorytmu, chronić je przed odczytem i przechowywać poza kodem aplikacji,
  • nie umieszczać w treści tokenu danych, które nie powinny trafić do użytkownika,
  • utrzymywać krótki czas życia tokenu dostępowego i odnawiać dostęp za pomocą tokenu odświeżającego, który można unieważnić po stronie serwera,
  • zapewnić możliwość natychmiastowego unieważnienia dostępu, na przykład listą unieważnionych tokenów albo licznikiem wersji sesji przypisanym do konta.

Wgląd i monitorowanie

  • udostępnić użytkownikowi listę aktywnych sesji wraz z czasem, adresem sieciowym i urządzeniem,
  • umożliwić zakończenie wybranej sesji oraz wszystkich pozostałych jednym działaniem,
  • powiadamiać o zalogowaniu z nowego urządzenia oraz o zmianie hasła i danych kontaktowych,
  • rejestrować utworzenie, odnowienie i zakończenie sesji bez zapisywania wartości jej identyfikatora,
  • monitorować równoległe sesje z odległych lokalizacji oraz nagłe zmiany urządzenia w trakcie sesji.

Dane powiązane z sesją

  • przechowywać dane zalogowanego użytkownika w sesji, nie w zmiennych globalnych ani polach statycznych współdzielonych przez wszystkie żądania,
  • oznaczać odpowiedzi zawierające dane zalogowanego użytkownika jako niebuforowalne, na przykład nagłówkiem Cache-Control: no-store.

Korzystaj z gotowych implementacji

Mechanizmu sesji nie należy budować samodzielnie. Popularne języki i frameworki mają sprawdzone biblioteki, które generują identyfikator, wiążą go ze stanem po stronie serwera i obsługują ciasteczko. W PHP sesję rozpoczyna session_start(), a funkcja session_destroy() usuwa jej dane po stronie serwera. Podstawowe atrybuty można ustawić w konfiguracji:

session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = Lax
session.use_strict_mode = 1
session.use_only_cookies = 1

Inne środowiska oferują analogiczne mechanizmy. Trzeba jednak sprawdzić ich ustawienia domyślne, ponieważ zależą od frameworka i jego wersji.