Przejdź do treści
INDEKS ANALIZ BREACHROAD / NOTA TECHNICZNA

Spring Security CVE-2026-41707: limit cache DPoP otwierał drogę do replay przechwyconego proofa

DPoPProofJwtDecoderFactory mógł zapomnieć użyte jti po zalaniu cache. Wyjaśniamy warunki replay, poprawione wersje i monitoring OAuth 2.0.

MATERIAŁ PUBLICZNY
AUTOR
/ CEO Breachroad · OSCP · PNPT
PUBLIKACJA
26 sierpnia 2026
CZAS CZYTANIA
16 min czytania
TEMAT
Tożsamość i dostęp
Spring Security CVE-2026-41707: limit cache DPoP otwierał drogę do replay przechwyconego proofa

CVE-2026-41707 dotyczy DPoPProofJwtDecoderFactory w Spring Security. Komponent zapamiętywał jti zaakceptowanych proofów DPoP, aby ten sam podpisany JWT nie mógł zostać użyty ponownie. Cache miał jednak ścisły limit rozmiaru. Napastnik dysponujący przechwyconym, prawidłowym proofem mógł zapełnić cache innymi żądaniami, wyprzeć chroniony wpis, a następnie odtworzyć oryginalny proof.

Spring ocenił podatność jako High, CVSS 3.1 7,4. Biuletyn producenta pochodzi z 20 sierpnia, a rekord CVE trafił do publicznego katalogu w nocnym cyklu 26 sierpnia czasu polskiego. Podatne są Spring Security 7.1.0, 7.0.0–7.0.6 i 6.5.0–6.5.11. Otwarte poprawione wersje to 7.1.1 i 7.0.7; dla wybranych starszych linii Spring wskazuje wydania Enterprise Support.

Co DPoP próbuje ochronić

W typowym OAuth 2.0 bearer token może zostać użyty przez każdego, kto pozna jego wartość. DPoP, czyli Demonstrating Proof of Possession, wiąże użycie tokenu z kluczem kryptograficznym klienta. Klient dołącza podpisany proof JWT zawierający między innymi metodę HTTP, URI docelowe, czas i unikalny identyfikator jti. Serwer weryfikuje podpis oraz zgodność kontekstu żądania.

Sam podpis nie zatrzymuje powtórki tego samego poprawnego komunikatu. Dlatego jti i okno czasowe stanowią ochronę replay: po zaakceptowaniu identyfikatora resource server powinien rozpoznać kolejne użycie. Jest to stan bezpieczeństwa. Jeśli implementacja zapomni jti zanim proof przestanie być akceptowalny, stary podpis staje się ponownie świeży z punktu widzenia walidatora.

DPoP ogranicza skutki kradzieży access tokenu bez klucza prywatnego, ale CVE nie oznacza pełnego złamania DPoP z samego dostępu do sieci. Advisory wyraźnie zakłada, że napastnik potrafi przechwycić prawidłowy proof użytkownika. Musi następnie doprowadzić do wyparcia wpisu i odtworzyć proof, gdy pozostałe kontrole — czas, metoda, URI i powiązanie z tokenem — nadal go dopuszczają.

Dlaczego ograniczony cache był błędem bezpieczeństwa

Cache o maksymalnej liczbie elementów jest zwykle mechanizmem ochrony dostępności. Zapobiega nieograniczonemu wzrostowi pamięci pod wpływem unikalnych danych od klientów. Problem pojawia się, gdy jego zawartość jest jedyną pamięcią o wykorzystanym poświadczeniu. Polityka eviction optymalizuje zasoby, a nie inwariant „ten identyfikator pozostaje zużyty przez całe okno replay”.

Jeżeli atakujący może dostarczać wystarczająco wiele syntaktycznie akceptowalnych, unikalnych jti, wypiera wcześniejsze elementy. Nawet algorytm LRU nie odróżnia wpisu użytkownika od szumu napastnika. Po usunięciu identyfikatora dekoder nie ma dowodu, że już go widział, więc powtórny proof przechodzi jak pierwszy.

To klasyczny konflikt między bounded memory a stateful security. Bezpieczna konstrukcja musi dobrać pojemność do maksymalnego tempa zaakceptowanych proofów i długości okna albo przechowywać stan w magazynie z TTL, którego semantyka nie usuwa wpisów przed terminem. Alternatywą jest odrzucanie nowych proofów przy wyczerpaniu bezpiecznej pojemności, choć przenosi to ryzyko w stronę denial of service. Nie ma darmowego eviction, gdy każdy wpis jest decyzją bezpieczeństwa.

Warunki rzeczywistej eksploatacji

Najpierw aplikacja musi używać podatnej klasy DPoPProofJwtDecoderFactory, a nie tylko mieć Spring Security w zależnościach. Następnie endpoint musi akceptować DPoP i pozwalać napastnikowi wygenerować ruch prowadzący do wielu nowych wpisów. Wreszcie atakujący potrzebuje prawidłowego proofa ofiary — na przykład przez kompromitację klienta, logowanie zbyt pełnych nagłówków, podatne proxy lub inną lukę w kanale.

CVSS ma Attack Complexity High, co odzwierciedla te warunki. Nie jest to powód do ignorowania patcha. DPoP wdraża się właśnie w systemach, które chcą ograniczyć skutek przechwycenia tokenów. Jeżeli warstwa antyreplay zawodzi w scenariuszu przechwycenia, nie spełnia najważniejszego założenia biznesowego tej funkcji.

Skutek opisany przez Spring to nieautoryzowany dostęp i podszycie się pod ofiarę, z wysokim wpływem na poufność i integralność oraz bez bezpośredniego wpływu na dostępność w wektorze CVSS. Zakres danych i operacji zależy od access tokenu, URI oraz metody związanej z przechwyconym proofem.

Które wersje naprawiają problem

Spring wymienia podatne 7.1.0, 7.0.0–7.0.6 oraz 6.5.0–6.5.11. Dla użytkowników open source właściwymi celami są 7.1.1 albo 7.0.7. W tabeli producenta znajdują się również 7.1.0.1, 7.0.6.1 i 6.5.12 dostępne w odpowiednich kanałach Enterprise Support.

W aplikacji Spring Boot trzeba sprawdzić rozwiązaną wersję modułów spring-security-*. Numer Boot i deklaracja BOM mogą wskazywać linię, ale override zależności, parent POM, lockfile lub cache obrazu mogą zmienić runtime. Najpewniejsze źródła to dependency tree z procesu budowy, SBOM obrazu i manifest faktycznie wdrożonego artefaktu.

Producent stwierdza, że po aktualizacji nie są potrzebne dalsze środki łagodzące. To informacja o usunięciu błędu w bibliotece, nie polecenie rezygnacji z monitoringu. Jeżeli środowisko działało na podatnej wersji, nadal trzeba ocenić, czy proofy lub tokeny mogły wyciec i czy w logach widać presję na cache.

Bezpieczny plan wdrożenia

Zidentyfikuj wszystkie resource servery korzystające z DPoP i ustal, czy instancje dzielą pamięć replay. W klastrze lokalny cache każdego poda oznacza inną perspektywę na wykorzystane jti; load balancer może kierować powtórki do wielu instancji. Ta właściwość nie jest treścią CVE, ale powinna wejść do przeglądu architektury antyreplay.

Podnieś zależność do poprawionej wersji, przebuduj artefakt i obraz od czystej warstwy, a następnie zweryfikuj dependency tree. Test regresyjny powinien potwierdzić akceptację pierwszego proofa, odrzucenie powtórki, poprawne związanie metody i URI oraz stabilność ochrony przy dużej liczbie unikalnych identyfikatorów. Nie publikuj kodu proof-of-concept na środowisku produkcyjnym; wykonaj kontrolowane testy w izolowanym stagingu.

Wdrażaj canary i obserwuj błędy DPoP, latencję, pamięć oraz odsetek 401/403. Nagły wzrost odrzuceń po aktualizacji może ujawnić klienta błędnie ponawiającego ten sam proof albo wcześniej niewidoczny ruch replay. Nie rozluźniaj walidacji, aby szybko uciszyć błędy — popraw generowanie proofów w kliencie.

Detekcja i dochodzenie

Logi powinny umożliwiać korelację zanonimizowanego lub zahaszowanego jti, odcisku klucza, klienta, metody, znormalizowanego URI i wyniku weryfikacji. Nie zapisuj pełnych access tokenów ani proofów, ponieważ log stałby się źródłem poświadczenia wymaganego przez atak. Retencja identyfikatorów musi pokrywać co najmniej dozwolone okno czasowe i potrzeby dochodzenia.

Szukaj nagłego wzrostu liczby unikalnych jti z jednego klienta, klucza lub źródła, po którym wraca wcześniej widziany identyfikator. Rozproszenie ruchu po adresach IP nie powinno rozbić korelacji opartej na kluczu i client ID. Warto także wykrywać dokładne powtórzenie kombinacji metoda–URI–jti po nietypowym wolumenie.

Jeżeli potwierdzisz replay, unieważnij powiązany access token, zbadaj źródło przechwycenia proofa i w razie potrzeby wymień klucz klienta. Sam patch usuwa drogę wyparcia wpisu, ale nie naprawia logowania sekretów, kompromitacji urządzenia ani proxy kopiującego wrażliwe nagłówki do niezaufanego systemu obserwowalności.

Lekcja projektowa: pamięć bezpieczeństwa ma swój czas życia

Rate limiting, nonces, jti, listy unieważnień i jednorazowe tokeny przechowują negatywną wiedzę: „tego już nie wolno zaakceptować”. Taki stan nie może być usuwany na podstawie zwykłej presji cache przed końcem okresu, w którym decyzja jest ważna. Pojemność należy projektować z budżetu ruchu, TTL i zachowania przeciążeniowego.

Testy powinny obejmować nie tylko dwa identyczne żądania pod rząd. Trzeba umieścić między nimi wolumen unikalnych elementów większy niż typowa pojemność, restart procesu, przełączenie poda i różne ścieżki load balancera. Właśnie w tych warunkach ujawnia się różnica między funkcjonalnym cache a trwałą barierą replay.

Fakty źródłowe i wnioski Breachroad

Mechanizm eviction, warunek przechwycenia poprawnego proofa, skutek, CVSS, zakres wersji i wydania naprawcze pochodzą z biuletynu Spring oraz rekordu CVE. Źródła nie podają aktywnej eksploatacji. Zalecenia dotyczące telemetrii, klastrów, testów obciążających stan i reagowania na wyciek są wnioskami Breachroad.

Szkolenia z OAuth, tożsamości i secure coding pomagają zespołom projektować poprawne granice replay, a testy aplikacji i API mogą sprawdzić implementację DPoP, proxy i zachowanie resource servera pod obciążeniem.

Źródła

UDOSTĘPNIJ / KOPIUJ