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

OAuth CVE-2026-19349, 72887 i 72888: state bez użytkownika, utracony verifier i cache bez limitu

Trzy błędy w LemonLDAP::NG i Net::OAuth pokazują, jak drobna semantyka state, oauth_verifier i dispatchu przed uwierzytelnieniem zmienia granicę SSO.

MATERIAŁ PUBLICZNY
AUTOR
/ CEO Breachroad · OSCP · PNPT
PUBLIKACJA
16 sierpnia 2026
CZAS CZYTANIA
16 min czytania
TEMAT
Tożsamość i dostęp
OAuth CVE-2026-19349, 72887 i 72888: state bez użytkownika, utracony verifier i cache bez limitu

16 sierpnia 2026 roku opublikowano trzy rekordy, które łączy temat protokołów tożsamości, choć dotyczą dwóch niezależnych projektów. CVE-2026-19349 opisuje bypass uwierzytelnienia w modułach GitHub i LinkedIn produktu LemonLDAP::NG. CVE-2026-72887 dotyczy cichego obniżenia OAuth 1.0a do OAuth 1.0 w bibliotece Perl Net::OAuth::Client, a CVE-2026-72888 — nieograniczonego cache nazw metod podpisu w Net::OAuth przed weryfikacją żądania.

Pierwsze dwa błędy naruszają spójność przepływu logowania. W LemonLDAP identyfikator sesji stanu mógł zacząć funkcjonować jak zwykła sesja SSO. W Net::OAuth brak potwierdzenia callbacku od dostawcy wyłączał tryb 1.0a i usuwał oauth_verifier z wymiany tokenu. Trzeci problem pokazuje, że kod wykonywany „jeszcze przed uwierzytelnieniem” także potrzebuje budżetu zasobów i ścisłej listy dopuszczalnych wartości.

Nie ma podstaw, by łączyć te CVE w jeden łańcuch ataku ani twierdzić, że dotyczą tej samej implementacji OAuth. Ich wspólna wartość edukacyjna jest jednak duża: token state, verifier i nazwa algorytmu nie są zwykłymi ciągami tekstu. Każde pole ma określoną rolę w maszynie stanów, a ciche przejście do słabszego znaczenia może usunąć kontrolę, której aplikacja oczekuje.

CVE-2026-19349: state zapisany jako sesja SSO

LemonLDAP::NG jest bramą SSO i menedżerem dostępu. W podatnych modułach uwierzytelnienia GitHub i LinkedIn portal przed przekierowaniem do dostawcy tworzył sesję dla parametru state. Wywołanie funkcji sesyjnej przekazywało jednak argumenty pozycyjne tam, gdzie funkcja oczekiwała identyfikatora i par nazwanych. Typ sesji domyślnie stawał się więc zwykłą sesją SSO zamiast krótkotrwałego stanu protokołu.

Identyfikator tej sesji był następnie przekazywany nieuwierzytelnionemu klientowi w adresie przekierowania. Według rekordu CVE-2026-19349 odwiedzający mógł użyć go jako cookie sesyjnego. Taka sesja nie zawierała _user ani authenticationLevel, lecz domyślna reguła default => accept w dostarczanej konfiguracji mogła mimo to zezwolić na wejście do wirtualnego hosta.

Podatność nie dotyczy każdej instalacji LemonLDAP. Wymaga włączonego modułu uwierzytelnienia GitHub albo LinkedIn oraz reguł, które akceptują pustą sesję bez sprawdzenia użytkownika lub poziomu uwierzytelnienia. Konfiguracje jawnie wymagające tożsamości i odpowiedniego authenticationLevel mają silniejszą drugą barierę.

Zakres wersji obejmuje gałęzie 2.0.0 przed 2.16.9, 2.17.0 przed 2.21.5 oraz 2.22.0 przed 2.23.3. Poprawki znajdują się odpowiednio w 2.16.9, 2.21.5 i 2.23.3. Administrator powinien wybrać najnowsze wspierane wydanie swojej gałęzi, a nie przenosić tylko pojedynczą zmianę bez testów całego logowania.

Dlaczego state nie jest sesją użytkownika

W OAuth i OpenID Connect state wiąże rozpoczęcie przepływu z jego zakończeniem i pomaga bronić się przed CSRF oraz pomieszaniem transakcji. Może być przechowywany po stronie serwera, lecz rekord takiego stanu powinien należeć do oddzielnej przestrzeni nazw, mieć krótki czas życia, być jednorazowy i nie dawać dostępu do zasobu.

Sesja użytkownika ma odwrotną semantykę: jest przedstawiana wielu żądaniom i służy jako dowód wcześniej zakończonego uwierzytelnienia. Użycie tego samego magazynu nie musi być błędem, ale typ rekordu i walidacja konsumenta muszą wykluczać zamianę ról. Losowy, trudny do odgadnięcia identyfikator nie pomaga, jeśli system sam ujawnia go klientowi jako element protokołu.

To właśnie dlatego kontrola dostępu nie powinna kończyć się na pytaniu „czy istnieje sesja?”. Brama musi sprawdzić, czy sesja ma użytkownika, oczekiwany mechanizm, poziom uwierzytelnienia, właściwy etap i ważność. Stan niekompletny powinien być odrzucany zamknięciem, nie traktowany jak anonimowa sesja zaakceptowana przez regułę domyślną.

CVE-2026-72887: OAuth 1.0a cicho wraca do 1.0

OAuth 1.0a dodał oauth_verifier, aby związać autoryzację użytkownika z późniejszą wymianą request tokenu na access token. Bez tego powiązania klasyczny OAuth 1.0 był podatny na session fixation. Aplikacja korzystająca z Net::OAuth::Client wybierała tryb 1.0a, gdy konfigurowała callback.

Advisory projektu pokazuje jednak, że odpowiedź dostawcy bez pola oauth_callback_confirmed wyłączała ten tryb bez wyjątku i ostrzeżenia. Biblioteka budowała następnie żądanie klasą OAuth 1.0, która nie miała parametru verifier, więc wartość przekazana przez aplikację znikała z komunikatu.

Istotne jest poprawne określenie napastnika. Sam złośliwy dostawca OAuth kontroluje własne konta, więc nie potrzebuje tego błędu, by manipulować swoim systemem. Realny przypadek to niekompletny lub starszy provider, który nie wysyła potwierdzenia, a osoba trzecia wykorzystuje przywróconą podatność session fixation: zdobywa request token, skłania ofiarę do jego autoryzacji i kończy wymianę w kontrolowanej przez siebie sesji.

Podatne są wersje Net-OAuth wcześniejsze niż 0.32; wydanie 0.32 zawiera poprawkę. Aplikacja deklarująca 1.0a powinna działać fail closed. Brak potwierdzenia callbacku nie jest negocjacją kompatybilności, lecz niespełnieniem wymaganego warunku bezpieczeństwa.

CVE-2026-72888: cache rośnie przed sprawdzeniem podpisu

Net::OAuth dynamicznie wybiera klasę implementującą metodę podpisu. Funkcja smart_require zapamiętywała w globalnym hashu każdą nazwę klasy, także taką, której załadowanie zakończyło się błędem. Nazwa powstawała z parametru signature_method dostarczonego przez klienta i była rozwiązywana przed weryfikacją podpisu.

Drugie advisory Net::OAuth opisuje monotoniczny wzrost pamięci w trwałym procesie Perl. Nieuwierzytelniony klient mógł wysyłać kolejne unikalne, długie nazwy. Każda pozostawała w cache na życie workera, a dodatkowo wymuszała wyszukiwanie modułu i kompilację tekstowego eval.

Limity nagłówków ograniczają koszt, gdy parametr pochodzi z Authorization, ale ścieżka POST może mieć inny limit. Reverse proxy nie zastępuje więc walidacji w bibliotece. Najlepsza poprawka to allowlista obsługiwanych metod podpisu przed dynamicznym dispatchem, cache wyłącznie poprawnie załadowanych klas i limit rozmiaru całego żądania.

Co zrobić teraz

Administratorzy LemonLDAP::NG powinni potwierdzić, czy używają backendu GitHub lub LinkedIn, zaktualizować odpowiednią gałąź i przejrzeć reguły dostępu. Każdy chroniony host powinien wymagać konkretnej tożsamości lub poziomu uwierzytelnienia. Reguła anonimowego accept nie powinna osłaniać zasobu, który organizacja uważa za zalogowany.

Po aktualizacji unieważnij podejrzane i niekompletne sesje, jeżeli magazyn nie pozwala jednoznacznie odróżnić ich rodzaju. Przejrzyj logi pod kątem sesji bez użytkownika, które otrzymały dostęp do chronionych hostów, oraz nietypowego ponownego użycia identyfikatora state jako cookie. Nie zapisuj pełnych tokenów w nowych logach; wystarczy bezpieczny skrót identyfikatora i typ sesji.

Użytkownicy Net::OAuth powinni przejść do 0.32 lub nowszej wersji. Test integracyjny ma symulować brak oauth_callback_confirmed i oczekiwać kontrolowanego błędu, nie udanego fallbacku. Test serwera powinien odrzucać nieznaną metodę podpisu przed próbą dynamicznego załadowania klasy i potwierdzać stabilne użycie pamięci workera.

Zmapuj również aplikacje korzystające z protokołu OAuth 1.0a. Jest starszy, ale nadal spotykany w integracjach, które rzadko przechodzą przegląd. SBOM pakietów Perl, analiza cpanfile i obrazu produkcyjnego mogą ujawnić kopię biblioteki niewidoczną w głównym repozytorium aplikacji.

Jak projektować bezpieczniejszy przepływ

Każdy artefakt protokołu powinien mieć jawny typ i etap. Rekord state nie może być zaakceptowany przez walidator sesji użytkownika. Callback musi zużyć state dokładnie raz, związać go z przeglądarką inicjującą, zweryfikować dostawcę i dopiero wtedy utworzyć nową sesję uwierzytelnioną.

Negocjacja wersji bezpieczeństwa powinna być monotoniczna: komponent może odrzucić zbyt słabego partnera, lecz nie może cicho wyłączyć zabezpieczenia zamówionego przez aplikację. Jeśli kompatybilność wymaga starszego protokołu, decyzja musi być jawna, ograniczona do konkretnego dostawcy i otoczona osobną analizą ryzyka.

Kod przed uwierzytelnieniem powinien używać allowlist i stałych kosztów. Dynamiczna nazwa klasy, algorytmu czy handlera pochodząca z sieci jest ryzykowna nawet wtedy, gdy później zostanie sprawdzony podpis. Weryfikacja wejścia musi poprzedzać kosztowny dispatch oraz zapis do długowiecznego cache.

Fakty i wnioski Breachroad

Faktem jest, że CVE-2026-19349 dotyczy modułów GitHub i LinkedIn LemonLDAP::NG oraz jest poprawione w 2.16.9, 2.21.5 i 2.23.3. Faktem jest, że oba problemy Net::OAuth dotyczą wersji przed 0.32, a wydanie 0.32 je naprawia. Źródła nie dowodzą powszechnego aktywnego wykorzystania.

Wnioskiem Breachroad jest traktowanie SSO jako maszyny stanów, a nie zestawu losowych tokenów. Najgroźniejsze błędy pojawiają się wtedy, gdy poprawny kryptograficznie identyfikator zostaje zaakceptowany w niewłaściwej roli albo kontrola jest usuwana przez automatyczny fallback.

Zespołom odpowiedzialnym za logowanie i integracje polecamy szkolenia cyberbezpieczeństwa dla organizacji. Granice sesji, callbacków i autoryzacji można dodatkowo sprawdzić podczas testów penetracyjnych aplikacji webowych i API.

UDOSTĘPNIJ / KOPIUJ