Tokeny JSON Web Token (JWT) są powszechnie używane do uwierzytelniania w API i jednokrotnego logowania (SSO). Format jest prosty, ale wiele decyzji dotyczących bezpieczeństwa pozostawia programiście i bibliotece. RFC 8725, czyli JWT Best Current Practices, zbiera znane błędy i sposoby ich unikania; w tym artykule omawiamy te, które sprawdzilibyśmy w pierwszej kolejności.
JWT jest podpisany, a nie tajny
Podpisany JWT, zdefiniowany w RFC 7519, składa się z trzech części rozdzielonych kropkami: nagłówka, ładunku (payload) z oświadczeniami (claims) i podpisu. Pierwsze dwie są jedynie zakodowane w base64url. Każdy, kto ma token, może je odczytać. Oto zdekodowany przykład:
{
"alg": "RS256",
"typ": "at+jwt",
"kid": "2026-10-key-1"
}
{
"iss": "https://auth.example.eu",
"sub": "user-4821",
"aud": "api.example.eu",
"iat": 1792140000,
"exp": 1792140900,
"role": "editor"
}
Podpis dowodzi, że oświadczenia nie zostały zmienione. Nie ukrywa ich. RFC 7519 stanowi, że gdy token zawiera informacje wrażliwe z punktu widzenia prywatności, należy podjąć środki zapobiegające ich ujawnieniu, np. użyć zaszyfrowanego tokenu (JWE). W praktyce w ładunku nie należy umieszczać danych osobowych, wewnętrznych identyfikatorów, których nie opublikowalibyśmy, ani niczego tajnego.
Pułapka 1 – akceptowanie "alg": "none"
RFC 7519 dopuszcza „niezabezpieczone” tokeny JWT z "alg": "none" i pustym podpisem. RFC 8725 opisuje biblioteki, które akceptowały takie tokeny i całkowicie pomijały weryfikację podpisu. Atakujący mógł wtedy wpisać dowolne oświadczenia.
RFC 7518 stanowi, że implementacje nie mogą domyślnie akceptować niezabezpieczonych tokenów, a OWASP REST Security Cheat Sheet zaleca, aby w ogóle ich nie dopuszczać. Należy sprawdzić, czy biblioteka odrzuca none, i przetestować to specjalnie przygotowanym tokenem.
Pułapka 2 – pozwalanie tokenowi wybrać algorytm
Wartość alg w nagłówku wybiera ten, kto utworzył token, a może to być atakujący. RFC 8725 opisuje klasyczny atak polegający na pomyleniu algorytmów: serwer oczekuje RS256, weryfikowanego kluczem publicznym RSA. Atakujący zmienia alg na HS256 i podpisuje token za pomocą HMAC, używając tego klucza publicznego jako sekretu. Biblioteka, która kieruje się nagłówkiem, pomyślnie go weryfikuje, a klucz publiczny jest z definicji publiczny.
Rozwiązaniem jest ustalenie algorytmu po stronie serwera. OWASP stwierdza, że weryfikacja nie może polegać na nagłówku JWT przy wyborze algorytmu. RFC 8725 dodaje, że każdy klucz powinien być używany z dokładnie jednym algorytmem. W kodzie wygląda to mniej więcej tak:
verify(token,
key = issuer_public_key,
algorithms = ["RS256"],
issuer = "https://auth.example.eu",
audience = "api.example.eu")
Pułapka 3 – słabe sekrety HMAC
Przy HS256 każdy, kto przechwyci token, może próbować odgadnąć sekret offline, tak szybko, jak pozwala mu sprzęt. RFC 8725 stanowi, że hasła, które da się zapamiętać, nie mogą być bezpośrednio używane jako klucze HMAC. RFC 7518 wymaga klucza co najmniej tak długiego jak wynik funkcji skrótu, czyli 256 bitów dla HS256.
openssl rand -base64 32
Daje to 32 losowe bajty, czyli 256 bitów. Należy przechowywać je w menedżerze sekretów, a nie w repozytorium.
Wspólne sekrety mają drugą słabość. Każda usługa, która może zweryfikować token HMAC, może też go utworzyć. Z tego powodu OWASP przedkłada podpisy nad kody MAC: jeśli tokeny weryfikuje kilka usług, przejęcie jednej z nich pozwoliłoby w przeciwnym razie wystawiać tokeny dla wszystkich. Algorytmy asymetryczne, takie jak RS256 czy ES256, pozwalają udostępniać wyłącznie klucz publiczny.
Pułapka 4 – pomijanie walidacji oświadczeń
Prawidłowy podpis dowodzi jedynie, kto wystawił token. To oświadczenia decydują, czy ta usługa powinna go teraz zaakceptować.
exp– według RFC 7519 token nie może być akceptowany w tym momencie ani później. Dopuszczalna jest niewielka tolerancja na rozbieżność zegarów, zwykle nie większa niż kilka minut.nbf– jeśli występuje, token należy odrzucać przed tym czasem.iss– należy sprawdzić, czy wystawca jest tym, któremu ufamy, i czy to on jest właścicielem klucza podpisującego.aud– według RFC 7519, jeśli usługa nie znajdzie siebie wśród odbiorców, token musi zostać odrzucony.
OWASP zaleca, aby w tokenach dostępu do API domyślnie wymagać i weryfikować iss, aud oraz exp. RFC 8725 wyjaśnia, dlaczego odbiorca ma znaczenie. Bez niego token wystawiony dla jednej usługi można odtworzyć w innej, która ufa temu samemu wystawcy. Zaleca też jawne typowanie za pomocą nagłówka typ, aby tokenu jednego rodzaju nie dało się użyć tam, gdzie oczekiwany jest inny. W przypadku tokenów dostępu OAuth RFC 9068 używa at+jwt, jak w powyższym przykładzie.
Pułapka 5 – zaufanie do kid, jku i x5u
Parametry nagłówka, które mówią serwerowi, gdzie znaleźć klucz, również kontroluje atakujący. RFC 8725 wymienia zagrożenia:
kid(identyfikator klucza) użyty w wyszukiwaniu w bazie danych lub katalogu może umożliwić wstrzyknięcie SQL lub LDAP. To samo dotyczy użycia go do zbudowania ścieżki pliku.jkuix5uwskazują adres URL, z którego pobierane są klucze. Podążanie za dowolnym adresem umożliwia fałszowanie żądań po stronie serwera (SSRF), a atakujący może wskazać własny klucz.
kid należy traktować jako nieprzezroczystą etykietę i wyszukiwać go w stałej liście kontrolowanych kluczy. Klucze należy pobierać wyłącznie z listy dozwolonych adresów URL, a RFC 8725 dodaje, że z tymi żądaniami nie powinny być wysyłane pliki cookie.
Pułapka 6 – długo ważne tokeny i brak unieważniania
JWT pozostaje ważny do chwili wygaśnięcia, nawet po wylogowaniu użytkownika lub zablokowaniu konta przez administratora. OWASP zauważa, że tokeny mogą przetrwać sesję, którą reprezentują.
- Tokeny dostępu powinny być krótkotrwałe, ważne przez minuty, a nie dni.
- Należy używać tokenów odświeżania, które serwer sprawdza przy każdym użyciu i może unieważnić.
- Przy wylogowaniu i zawieszeniu konta należy prowadzić listę zablokowanych identyfikatorów tokenów (
jti) do czasu ich wygaśnięcia, jak sugeruje OWASP. - Klucze podpisujące należy wymieniać zgodnie z harmonogramem i mieć przetestowaną procedurę awaryjnej wymiany.
Pułapka 7 – przechowywanie tokenów tam, gdzie mogą je odczytać skrypty
Tokeny przechowywane w localStorage może odczytać dowolny kod JavaScript na stronie. OWASP HTML5 Security Cheat Sheet ostrzega, że jedna podatność typu cross-site scripting wystarczy, aby wykraść wszystko z tych obiektów pamięci, i odradza przechowywanie w nich identyfikatorów sesji.
W aplikacjach przeglądarkowych plik cookie z atrybutami HttpOnly i Secure oraz odpowiednim ustawieniem SameSite trzyma token poza zasięgiem skryptów. Pliki cookie wprowadzają jednak ryzyko fałszowania żądań między witrynami (CSRF), więc trzeba to również uwzględnić. Niezależnie od wyboru silna polityka Content Security Policy zmniejsza ryzyko wystąpienia XSS.
Lista kontrolna walidacji
- Na serwerze ustalić z góry akceptowane algorytmy i odrzucać
none. - Używać biblioteki, która weryfikuje podpis, zanim odczytane zostaną jakiekolwiek oświadczenia.
- Przy każdym żądaniu weryfikować
exp,nbf,issiaud. - Używać
typlub odrębnych odbiorców, aby rozdzielić rodzaje tokenów. - Wyszukiwać klucze po
kidw stałym zbiorze; nigdy nie pobierać kluczy z dowolnych adresów URL. - Używać losowych kluczy HMAC o długości co najmniej 256 bitów albo kluczy asymetrycznych.
- Nie umieszczać w ładunkach sekretów ani zbędnych danych osobowych.
- Utrzymywać krótki czas ważności tokenów dostępu i zapewnić możliwość ich unieważniania.
- Przechowywać tokeny w przeglądarce poza zasięgiem JavaScriptu.
Od czego zacząć
Token ze środowiska testowego można wkleić do naszego dekodera JWT, aby zobaczyć, co dokładnie ujawnia, i porównać jego wartości alg, aud i exp z tą listą. Nie należy wklejać aktywnych tokenów produkcyjnych do żadnego narzędzia online.
Jeśli potrzebują Państwo drugiej opinii na temat projektu uwierzytelniania lub przeglądu kodu obsługującego tokeny, pomoże nasz zespół bezpiecznego oprogramowania.
