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

qs 6.16.0: dwie luki DoS w parserze używanym przez aplikacje Node.js

CVE-2026-82417 i CVE-2026-82562 pokazują, jak nietypowy kształt obiektu oraz przecinki w a[] mogą ominąć założenia parsera i wywołać awarię lub presję pamięci.

MATERIAŁ PUBLICZNY
AUTOR
/ CEO Breachroad · OSCP · PNPT
PUBLIKACJA
30 sierpnia 2026
CZAS CZYTANIA
18 min czytania
TEMAT
Łańcuch dostaw
qs 6.16.0: dwie luki DoS w parserze używanym przez aplikacje Node.js

Dwa nowe rekordy dotyczące pakietu qs pojawiły się w NVD 30 sierpnia czasu polskiego. CVE-2026-82417 może doprowadzić do wyjątku podczas ponownej serializacji danych kontrolowanych przez klienta. CVE-2026-82562 omija deklarowany limit tablic dla jednego wariantu składni z nawiasami i przecinkami. Oba problemy poprawiono w qs 6.16.0.

Nie są to luki pozwalające wykonać kod ani odczytać bazę. Ich podstawowym skutkiem jest utrata dostępności: odpowiedź HTTP 500, zakończenie procesu roboczego albo zużycie pamięci proporcjonalne do rozmiaru wejścia. Są jednak ważne z powodu miejsca pakietu w ekosystemie. qs tłumaczy złożone query stringi i ciała application/x-www-form-urlencoded na obiekty JavaScript. Może działać wewnątrz frameworka, bramy API albo kodu pośredniczącego, nawet jeśli zespół nie importuje go bezpośrednio.

Dzisiejszą nowością jest publikacja jednoznacznych rekordów CVE i ich wejście do procesów zarządzania podatnościami. Advisory projektu dla obu problemów noszą datę 29 sierpnia, natomiast NVD zarejestrowało je już 30 sierpnia czasu warszawskiego. Ta różnica nie zmienia pilności aktualizacji, ale pozwala precyzyjnie opisać chronologię.

CVE-2026-82417: niebezpieczny kształt obiektu zamiast prawdziwego bufora

Pierwszy błąd znajduje się na granicy między parse() i stringify(). Podczas serializacji wartości qs.stringify() sprawdza, czy obiekt jest buforem. Podatna implementacja odczytywała obj.constructor.isBuffer, sprawdzała jedynie, czy pole ma wartość logicznie prawdziwą, a następnie wywoływała je jak funkcję.

Problem w tym, że constructor i isBuffer mogą być zwykłymi kluczami danych. Jeżeli wejście zbuduje obiekt, w którym isBuffer jest napisem lub inną wartością niewywoływalną, kontrola kończy się TypeError. Nie trzeba tworzyć prawdziwego obiektu Buffer ani zmieniać prototypu. Wystarczy kształt danych, który po przejściu przez odpowiednią konfigurację parsera zawiera własne pole constructor.isBuffer.

Domyślna konfiguracja qs.parse() odrzuca klucz constructor, ale opcje plainObjects: true lub allowPrototypes: true pozwalają go zachować. Advisory wskazuje również, że Express 4 i body-parser w pewnych typowych ścieżkach korzystają z allowPrototypes: true. To nie oznacza, że każda aplikacja Express jest podatna. Zagrożona ścieżka musi później przekazać taki obiekt do qs.stringify() — na przykład podczas budowania przekierowania, żądania do usługi upstream, klucza cache albo znormalizowanego adresu.

Skutek zależy od obsługi błędów. Wyjątek przechwycony przez warstwę frameworka zwykle psuje pojedyncze żądanie. Wyjątek w niechronionej kontynuacji asynchronicznej lub zadaniu w tle może zakończyć worker. CVE otrzymało CVSS 4.0: 6,3 i CVSS 3.1: 5,3; wpływ dotyczy dostępności, a nie poufności czy integralności.

Zakres podatny jest szeroki: qs od 2.2.5 do 6.15.3, a poprawka znajduje się w 6.16.0. Commit naprawczy wymaga, aby constructor.isBuffer rzeczywiście było funkcją przed wywołaniem. Ta mała zmiana ilustruje większą regułę: duck typing na danych niezaufanych jest decyzją bezpieczeństwa, nie tylko wygodą programistyczną.

CVE-2026-82562: limit tablic działał dla jednego zapisu, ale nie dla a[]

Druga luka dotyczy konfiguracji comma: true i throwOnLimitExceeded: true. Pierwsza opcja rozdziela wartości po przecinkach na tablicę, druga ma zatrzymać parsowanie po przekroczeniu arrayLimit. Dla płaskiego klucza limit działał, lecz odpowiednik zapisany jako a[]=... przechodził inną gałęzią.

Parser najpierw dzielił wartość po przecinkach, a następnie opakowywał wynik w jednoelementową tablicę zewnętrzną. Kontrola długości widziała opakowanie o długości jeden zamiast tablicy wewnętrznej. Atakujący mógł więc umieścić wiele elementów w jednym parametrze a[], mimo że aplikacja jawnie włączyła twarde odrzucanie wartości ponad limit.

To istotny szczegół: comma oraz throwOnLimitExceeded nie są jednocześnie konfiguracją domyślną. Ponadto koszt rośnie liniowo z liczbą bajtów dostarczonych przez klienta. Domyślny limit długości linii żądania w Node.js utrudnia wielomegabajtowe query stringi, ale podatny parser może również otrzymać ciało formularza, jeśli wcześniejsza warstwa zezwala na duże żądania.

CVE-2026-82562 obejmuje wersje 6.14.2–6.15.3 i również jest poprawione w 6.16.0. NVD podaje CVSS 4.0: 6,3 oraz CVSS 3.1: 3,7. Rekord opisuje nieograniczoną alokację zasobów względem zamierzonej polityki, lecz nie „mały pakiet powodujący nieskończone zużycie RAM”. Realny koszt nadal ogranicza maksymalny rozmiar wejścia.

Luka jest kontynuacją wcześniejszej pracy nad CVE-2026-2391. Poprawka 6.14.2 dodała kontrolę wartości przecinkowych, ale nie objęła dokładnie wariantu []=. To cenna lekcja dla AppSec: test regresji powinien pokrywać równoważne semantycznie zapisy, a nie tylko jeden przykład użyty w pierwotnym raporcie.

Jak ustalić rzeczywistą ekspozycję

Samo znalezienie qs w lockfile nie wystarcza do oceny ryzyka. Najpierw ustal wersję faktycznie dołączoną do artefaktu produkcyjnego. W monorepo mogą współistnieć różne kopie, a deklaracja pośrednia może zostać rozwiązana inaczej w zależności od menedżera pakietów, override’ów i stanu lockfile. Sprawdź SBOM obrazu lub paczki, drzewo zależności oraz pliki lock użyte w konkretnym buildzie.

Dla CVE-2026-82417 znajdź przepływy parse → transformacja → stringify. Szczególnie ważne są reverse proxy w kodzie aplikacji, bramy przepisujące parametry, generatory redirectów, klienty HTTP składające upstream URL oraz zadania asynchroniczne serializujące dane z żądania. Następnie sprawdź, czy parsowanie zachowuje klucze prototypowe lub tworzy obiekty bez prototypu.

Dla CVE-2026-82562 wyszukaj konfigurację comma: true i throwOnLimitExceeded: true. Oceń, czy parser otrzymuje query string, ciało formularza czy dane już przetworzone przez inny komponent. Zapisz limit nagłówków i maksymalny rozmiar body na CDN, load balancerze, ingressie, w frameworku i na samej trasie. Najmniejszy skuteczny limit jest rzeczywistą granicą kosztu.

Nie zakładaj, że skaner SCA rozstrzygnie osiągalność. SCA odpowie, czy wersja jest obecna. Dopiero analiza wywołań i konfiguracji pokaże, czy niezaufane dane docierają do wadliwej gałęzi.

Aktualizacja i zabezpieczenia warstwowe

Najlepszym rozwiązaniem jest aktualizacja do qs 6.16.0 lub nowszej wersji zaakceptowanej przez właściciela aplikacji. Ponieważ to zależność często pośrednia, może być potrzebne podniesienie frameworka albo kontrolowany override. Override nie powinien omijać testów zgodności: uruchom przypadki obejmujące query stringi, formularze, wielokrotne klucze, tablice, obiekty zagnieżdżone, przecinki i błędy limitów.

Jeśli aktualizacja nie może wejść natychmiast, wyłącz comma: true, gdy funkcja nie jest potrzebna, i unikaj ponownej serializacji nieprzefiltrowanych struktur klienta. Nie włączaj allowPrototypes bez udokumentowanej potrzeby. Na granicy HTTP ustaw ograniczenia rozmiaru query stringu i body, limity czasu oraz kontrolę współbieżności. Te mechanizmy zmniejszają wpływ CVE-2026-82562, ale nie zastępują poprawki CVE-2026-82417.

Obsługuj błędy w tej samej granicy asynchronicznej, w której mogą wystąpić. Restart procesu przez orchestrator poprawia dostępność, lecz nie usuwa podatności; bez limitowania napastnik może stale odtwarzać awarię.

Detekcja i bezpieczna walidacja

W logach aplikacji szukaj nagłego wzrostu TypeError wskazujących na isBuffer lub qs/lib/utils, serii odpowiedzi 500 na trasach przepisujących parametry oraz restartów workerów skorelowanych z podobnymi kształtami zapytań. Dla drugiej luki obserwuj nietypowo długie wartości rozdzielane przecinkami, klucze z [], presję sterty, wydłużone pauzy garbage collectora i odrzucenia z warstw limitujących body.

Walidację poprawki wykonuj w środowisku testowym za pomocą małych, nieszkodliwych przypadków przekraczających niski limit. Nie trzeba wywoływać wyczerpania pamięci, aby potwierdzić, że 6.16.0 rzuca kontrolowany RangeError dla wszystkich form klucza i nie wywołuje wartości niewywoływalnej jako isBuffer.

Fakty źródłowe i wnioski Breachroad

Zakres wersji, mechanizmy, oceny CVSS i wydanie naprawcze pochodzą z advisory projektu oraz rekordów NVD. Scenariusze inwentaryzacji, telemetria i kolejność działań są wnioskami obronnymi Breachroad. Źródła nie informują o potwierdzonym aktywnym wykorzystywaniu tych luk. Obecność podatnej wersji nie jest dowodem incydentu.

Źródła pierwotne

Takie błędy najlepiej rozumie się na styku kodu, frameworka i infrastruktury. Na szkoleniach z cyberbezpieczeństwa uczymy zespoły rozpoznawania granic danych i budowania bezpiecznych testów regresji. Organizacje potrzebujące oceny osiągalności podatności mogą objąć parsery, bramy i API kontrolowanymi testami penetracyjnymi web i API.

UDOSTĘPNIJ / KOPIUJ