Kliknięcie „Wyloguj” w systemie ERP klienta nie kończyło sesji. Aplikacja usuwała token JWT z przeglądarki, ale backend nie miał żadnego mechanizmu jego unieważniania, a czas życia tokena wynosił 24 godziny. Przechwycony token działał więc przez cały ten czas — niezależnie od tego, co robił prawowity użytkownik. Naprawa objęła skrócenie czasu życia tokena do 15 minut, wprowadzenie tokenów odświeżających i listę unieważnionych tokenów po stronie serwera.
Schemat powyżej: Po wdrożeniu deny listy backend sprawdza każdy token przed wykonaniem żądania i odrzuca ten, który został unieważniony — nawet jeśli kryptograficznie wciąż jest poprawny.
- Wylogowanie działało wyłącznie po stronie przeglądarki — token znikał z Local Storage, ale pozostawał ważny na serwerze.
- Czas życia tokena dostępowego wynosił 24 godziny, bez mechanizmu odświeżania.
- Backend nie prowadził listy unieważnionych tokenów, więc nie miał jak odrzucić skradzionego.
- Skutek: okno na wykorzystanie wykradzionego tokena było równe pełnej dobie, a nie sekundom.
Kontekst: „przecież kliknąłem wyloguj”
System ERP był sercem operacji klienta — zarządzał produkcją, magazynem i danymi kadrowymi. Zespół deweloperski wdrożył uwierzytelnianie oparte na JSON Web Tokens (JWT), żeby uzyskać bezstanowość i wydajność. Zabrakło jednej rzeczy: bezstanowość nie zwalnia z zarządzania cyklem życia sesji.
To rozróżnienie jest źródłem większości problemów z JWT, jakie spotykamy podczas testów penetracyjnych aplikacji webowych. Token, którego nie da się cofnąć, jest wygodny dla serwera i niebezpieczny dla organizacji.
Na czym polegał problem: tokeny wiecznie żywe
Aplikacja frontendowa poprawnie usuwała token z Local Storage po kliknięciu „Wyloguj”. Backend nie miał jednak żadnej listy unieważnionych tokenów, a ich czas życia ustawiono na 24 godziny. Z punktu widzenia serwera każdy podpisany, nieprzeterminowany token był dowodem tożsamości — bez względu na to, czy użytkownik uważał się za wylogowanego.
Jak pracowaliśmy: przechwycenie i ponowne użycie
W kontrolowanym środowisku odtworzyliśmy pełny scenariusz nadużycia.
- Użytkownik loguje się do systemu i otrzymuje token.
- Przechwytujemy token — symulując wyciek przez podatność XSS albo przez logi serwera proxy.
- Użytkownik klika „Wyloguj”.
- Wysyłamy żądanie do API z przechwyconym tokenem.
Backend zrealizował żądanie bez zastrzeżeń. Token był kryptograficznie poprawny, a jego czas życia jeszcze nie minął — z perspektywy serwera nic się nie wydarzyło.
Ryzyko biznesowe: dostęp z opóźnionym zapłonem
W razie wycieku tokena atakujący miał do 24 godzin swobodnego działania w systemie ERP, nawet jeśli użytkownik wylogował się minutę po kradzieży. W systemie tej klasy oznacza to możliwość odczytu i modyfikacji danych finansowych oraz dostęp do danych osobowych pracowników. Reakcja typu „natychmiast wyloguj wszystkich” nic by nie dała, bo nie było czego wylogowywać.
Co zaleciliśmy i co wdrożono
- Skrócenie czasu życia tokena dostępowego do 15 minut. Samo to zmniejsza okno nadużycia z doby do kwadransa.
- Wprowadzenie tokenów odświeżających (refresh tokens). Długo żyjący token trzymany jest po stronie serwera i można go w każdej chwili unieważnić.
- Lista unieważnionych tokenów (deny list) w pamięci podręcznej. Backend sprawdza w niej każdy token przed wykonaniem żądania; wpisy wygasają razem z tokenem, więc lista nie rośnie w nieskończoność.
Efekt: wylogowanie zaczęło działać
Po wdrożeniu poprawek kliknięcie „Wyloguj” faktycznie kończy sesję, a przechwycony token staje się bezużyteczny w tej samej sekundzie. Retest potwierdził, że żądanie z unieważnionym tokenem zwraca odpowiedź 401.
Jak zabezpieczyć się przed tym atakiem
Jeżeli wdrażasz uwierzytelnianie oparte na tokenach, pilnuj jednego: bezstanowość nie jest wymówką dla braku zarządzania sesją. Wymagaj mechanizmu unieważniania po stronie serwera. Czas życia dostępu licz w minutach, a długotrwałą sesję opieraj na mechanizmie odświeżania, który da się odciąć.
Warto też sprawdzić, co się dzieje przy zmianie hasła i przy odbieraniu uprawnień — w obu sytuacjach stare tokeny powinny przestać działać natychmiast, a nie po dobie.
Powiązane case studies
- „Zalogowany” nie znaczy „bezpieczny”: Session Fixation — Druga strona tego samego problemu: cykl życia sesji po stronie serwera.
- Wtyczka, która widziała za dużo: nadużycie OAuth — Nadmiarowe uprawnienia integracji i tokeny, których nikt nie odbiera.
- Jedno pole w GraphQL i nagle jesteś „innym klientem” — Podmiana identyfikatora w zapytaniu otwiera dane innego najemcy.
Jak Pentestica może pomóc
Mechanizmy uwierzytelniania i zarządzania sesją to obszar, w którym „działa” i „jest bezpieczne” to dwie różne rzeczy. Sprawdzamy je w ramach testów penetracyjnych aplikacji webowych, a szersza ocena procesów i konfiguracji należy do audytu bezpieczeństwa IT.

Redakcja Pentestica to zespół pentesterów i konsultantów bezpieczeństwa, którzy na co dzień wykonują testy penetracyjne, testy TLPT wymagane przez DORA, red teaming i audyty bezpieczeństwa IT, a także prowadzą wdrożenia zgodności z NIS2, DORA i MiCA. Teksty na tym blogu piszemy z tej praktyki, a nie z opracowań. Każde twierdzenie o przepisie, terminie albo karze opieramy na źródle pierwotnym: dzienniku ustaw, tekście dyrektywy, komunikacie organu nadzoru. Jeśli czegoś nie da się potwierdzić, piszemy o tym wprost, zamiast powtarzać liczbę krążącą po branżowych portalach.