Luka w Node.js: łatka Node2Shell i sprawdzanie wersji Node.js (WordPress)

Node2Shell to przydomek CVE-2026-111497, luki w Node.js, na którą łatkę wydano 21 października 2026 r.; oficjalny tytuł opisuje błąd zapisu poza granicami bufora w dekoderze Brotli od Google. Ponieważ rozpakowanie spreparowanych danych Brotli może prowadzić do zdalnego wykonania kodu, najpierw trzeba sprawdzić wersje Node.js, Bun i libbrotli na serwerze i zaktualizować je do wersji z łatką.
Na serwerze Nginx + Apache z WordPressem 7.1.3 sprawdzono, które pakiety używają dekodera Brotli i którędy zdalne żądania WordPressa przechodzą przez libbrotli.
1️⃣ Luka w Node.js Node2Shell: jak działa błąd i czego dotyczy
Nazwę Node2Shell nadał zespół badawczy firmy bezpieczeństwa Imperva w artykule z 22 października. Tytuł komunikatu na nodejs.org brzmi [Brotli decoder out-of-bounds write permits remote code execution]. Błąd tkwi w kodzie źródłowym dekodera Google Brotli wbudowanego w Node.js (c/dec/decode.c), więc poprawki w kodzie aplikacji go nie zablokują.
Według wpisu NVD dekoder Google Brotli w wersjach od 1.0.3 do 1.2.0 podczas rozpakowywania spreparowanego strumienia zapisuje dane za końcem bufora i uszkadza sąsiednią pamięć stanu dekodera. Przez ten błąd zdalny atakujący bez logowania może odczytać zawartość pamięci serwera, przerwać działanie usługi i wykonać dowolny kod.
Ocen wagi są dwie. Komunikat na nodejs.org nadał poziom High, a Google, które wydało CVE, przyznało 9,2 (Critical) w skali CVSS 4.0. Według stanu na 23 października NVD nie zakończyło jeszcze własnej analizy, więc wyniku NVD jeszcze nie ma. Typy słabości to niesprawdzana wartość zwracana (CWE-252) i zapis poza granicami bufora (CWE-787).
Według komunikatu bezpieczeństwa na nodejs.org luka dotyczy wszystkich wspieranych linii Node.js (22.x, 24.x i 26.x), a wersje z łatką to 22.23.4, 24.22.1 i 26.12.1. Ten sam komunikat podaje, że linie bez wsparcia przy każdym komunikacie bezpieczeństwa zawsze uznaje się za podatne.
Podatny jest każdy program, który używa tego samego dekodera, także poza Node.js. 21 października Google wydało trzy wersje biblioteki Brotli z poprawką tej samej CVE: 1.0.10, 1.1.1 i 1.2.1. Bun 22 października umieścił Brotli na liście poprawek bezpieczeństwa w ogłoszeniu wersji 1.4.4, a zgłoszenie w repozytorium podaje, że ta wersja zawiera poprawkę aktualizującą brotli.
| cel | Wersje podatne | Wersja z poprawką | Źródło |
|---|---|---|---|
| Node.js | Wszystkie wspierane linie 22.x · 24.x · 26.x · linie bez wsparcia | 22.23.4 · 24.22.1 · 26.12.1 | Komunikat bezpieczeństwa nodejs.org (2026-10-21) |
| Biblioteka Brotli od Google | 1.0.3 ~ 1.2.0 | 1.0.10 · 1.1.1 · 1.2.1 | Wpis NVD · wydania google/brotli (2026-10-21) |
| Bun | Brotli na liście poprawek bezpieczeństwa w ogłoszeniu wersji 1.4.4 | 1.4.4 | Ogłoszenie Bun (2026-10-22) |
| Pakiet libbrotli z dystrybucji | Pakiety zbudowane na Brotli od 1.0.3 do 1.2.0 | Wersja podana w komunikacie bezpieczeństwa dystrybucji (na 2026-10-23 jeszcze jej nie ma) | Strona trackera bezpieczeństwa każdej dystrybucji |
Sam Node.js nie rozpakowuje z własnej inicjatywy odebranych danych Brotli. Według komunikatu bezpieczeństwa firmy hostingowej Vercel narażone są miejsca, w których aplikacja lub biblioteka rozpakowuje skompresowane dane z zewnątrz. Przykładem jest wbudowany fetch(), gdy odbiera odpowiedź br, oraz Express 5 lub body-parser 2, gdy rozpakowują treść żądania br; domyślny serwer HTTP, który tylko odbiera dane, niczego nie rozpakowuje.
Według stanu na 23 października nie potwierdzono rzeczywistego wykorzystania luki. Tej CVE nie ma w katalogu znanych wykorzystywanych podatności CISA (KEV, wersja z 21 października). Etap wykorzystania, który CISA nadaje we wpisie CVE, zmienił się 23 października na [publiczny proof of concept (PoC)].
To, gdzie znajduje się dekoder, decyduje o tym, co trzeba zrobić. Oficjalne binaria Node.js mają dekoder wbudowany w plik wykonywalny, ale są też buildy korzystające z systemowej libbrotli, jak pakiet nodejs w Debianie. Dystrybucje Linuksa instalują ten sam kod źródłowy jako pakiet libbrotli, z którego wspólnie korzystają programy takie jak curl.
2️⃣ Witryny WordPress i PHP też są zagrożone: droga, którą zdalne żądania rozpakowują odpowiedzi przez libbrotli
Także na serwerze WordPress bez Node.js ten sam dekoder jest zainstalowany jako pakiet libbrotli. Według stanu na 23 października ani WordPress, ani curl, ani PHP nie wypowiedziały się oficjalnie o tej CVE i nie ma jej też na oficjalnej liście podatności curl. Dlatego ta sekcja potwierdza tylko strukturę na podstawie kodu rdzenia, dokumentacji curl i pomiarów na serwerze testowym.
Na zmierzonym serwerze Nginx + Apache bibliotekę dekodera (libbrotlidec) wymagało pięć pakietów, a pakietem powiązanym z WordPressem jest libcurl.
rpm -q --whatrequires 'libbrotlidec.so.1()(64bit)' | sed 's/-[0-9].*//'freetype
brotli-devel
libjxl
libcurl
libsouplibcurl wymaga libbrotli do rozpakowywania skompresowanych odpowiedzi HTTP. Według oficjalnej dokumentacji curl libcurl zbudowana z biblioteką brotli automatycznie rozpakowuje odpowiedzi z nagłówkiem Content-Encoding równym br. Tę libcurl bezpośrednio wywołuje rozszerzenie curl w PHP.
ldd /usr/lib64/libcurl.so.4 | grep brotli libbrotlidec.so.1 => /lib64/libbrotlidec.so.1 (0x00007f32803ed000)
libbrotlicommon.so.1 => /lib64/libbrotlicommon.so.1 (0x00007f327f858000)php -i | grep BROTLIBROTLI => YesWersja libcurl używana przez WordPressa widnieje w wierszu [Wersja cURL] po rozwinięciu sekcji [Serwer] na zakładce [Informacja] w [Narzędzia › Stan witryny] w panelu administracyjnym. Ten wiersz nie pokazuje, czy brotli jest obsługiwane, więc sprawdza się to poleceniami na serwerze, jak wyżej.
Wiersz [Wersja cURL] na obrazie pokazuje wersję libcurl razem z biblioteką SSL, z którą ją zbudowano. Według dokumentacji curl br jest obsługiwany od wersji 7.57.0; jeśli ta wartość jest niższa, libcurl nie rozpakuje odpowiedzi br. Jeśli jest równa lub wyższa, sprawdza się poleceniami na serwerze, czy brotli znalazło się w buildzie.
Biblioteka Requests w rdzeniu WordPressa przy żądaniach do zewnętrznych adresów ustawia wartość [CURLOPT_ENCODING] uchwytu curl na pusty ciąg. Według dokumentacji curl pusty ciąg oznacza akceptowanie wszystkich metod kompresji obsługiwanych przez daną libcurl. Jeśli więc drugi serwer odeśle odpowiedź skompresowaną br, rozpakuje ją libbrotli na serwerze WordPress.
grep -n CURLOPT_ENCODING wp-includes/Requests/src/Transport/Curl.php110: curl_setopt($this->handle, CURLOPT_ENCODING, '');
212: curl_setopt($this->handle, CURLOPT_ENCODING, 'none');Wiersz 110 to wartość domyślna dla wszystkich żądań, a wiersz 212 wykonuje się tylko wtedy, gdy żądanie, którego nie udało się rozpakować, jest wysyłane ponownie bez kompresji. Z zewnątrz nie da się tego uruchomić bezpośrednio; najpierw musiałby zostać przejęty serwer, z którego WordPress pobiera odpowiedzi, np. przy sprawdzaniu aktualizacji rdzenia i wtyczek, w oEmbed lub w kanałach RSS.
Przebieg zaczyna się od danych z zewnątrz skompresowanych br. Błąd zostaje wywołany na etapie, gdy fetch() w Node.js, Express 5, fetch() w Bun albo libbrotli wywoływana przez libcurl w WordPressie rozpakowuje te dane. Skutkiem jest wykonanie kodu na serwerze. Wersje z łatką blokują ten przebieg, więc trzeba zastąpić dekoder wersją z łatką.
Moduł mod_brotli w Apache linkuje tylko bibliotekę kodera (libbrotlienc), która kompresuje odpowiedzi, więc konfiguracja włączająca kompresję tym modułem nie leży na drodze błędu dekodera.
ldd /usr/lib64/httpd/modules/mod_brotli.so | grep brotli libbrotlienc.so.1 => /lib64/libbrotlienc.so.1 (0x00007f34b4b79000)
libbrotlicommon.so.1 => /lib64/libbrotlicommon.so.1 (0x00007f34b4a79000)3️⃣ Sprawdzanie wersji Node.js: czy na serwerze są Node.js, Bun i libbrotli i w jakich wersjach
Pracę zaczyna się od spisania, co jest zainstalowane na serwerze. W przypadku Node.js wersję sprawdza się poleceniem [node -v]; jeśli wartość jest niższa niż 22.23.4, 24.22.1 lub 26.12.1 dla danej linii, wersja jest podatna.
| Twoje środowisko | Polecenie sprawdzające | Co zostaje naprawione |
|---|---|---|
| Instalator z nodejs.org · nvm · oficjalny obraz Docker | [node -v] | Sam Node.js (22.23.4 · 24.22.1 · 26.12.1) |
| nodejs z dystrybucji · wiersz libbrotli w ldd | [ldd $(command -v node)] | Pakiet libbrotli |
| nodejs z dystrybucji · wynik ldd 0 | [ldd $(command -v node)] | Pakiet nodejs (po komunikacie bezpieczeństwa dystrybucji) |
| Node.js Selector w hostingu współdzielonym (CloudLinux alt-nodejs) | Komunikat dostawcy · numer release pakietu | Pakiet udostępniony przez dostawcę + ponowne uruchomienie aplikacji |
| Bun | [bun --version] | Bun 1.4.4 lub nowszy |
| Bez Node.js (tylko WordPress) | Sprawdzenie libcurl przez [ldd] | Pakiet libbrotli + restart php-fpm · do tego czasu zabezpieczenie tymczasowe |
| Hosting zarządzany | Komunikat dostawcy · zapytanie do pomocy technicznej | Pakiety aktualizowane przez dostawcę |
Na komputerze (Windows lub macOS) instaluje się ponownie instalatorem wersji z łatką z nodejs.org; w nvm sprawdza się wersję poleceniem [nvm ls], a potem instaluje ponownie według numeru linii, np. [nvm install 24]. Tagi 22, 24 i 26 oficjalnego obrazu Docker node przebudowano 22 października z wersjami z łatką, więc trzeba ponownie pobrać obraz, zbudować go i wdrożyć. Komunikat Vercel zaleca też aktualizację do wersji z łatką samodzielnie budowanych obrazów kontenerów, środowisk CI i lokalnych środowisk programistycznych.
Na serwerze, na którym zainstalowano Node.js ze strumienia dnf module, [node -v] pokazuje wersję z linii tego strumienia; nawet przy tej samej linii trzeba zaktualizować, jeśli końcowe cyfry nie sięgają wersji z łatką.
node -vv24.xx.xTo, czy Node.js używa systemowej libbrotli, czy wbudowanego dekodera, sprawdza się przez [ldd]. Jeśli w wyniku jest wiersz libbrotlidec, build korzysta z biblioteki współdzielonej; jeśli wynik to 0, jak niżej, dekoder jest wbudowany i błąd usuwa dopiero aktualizacja samego Node.js.
ldd $(command -v node) | grep -c brotli0W Bun wersję sprawdza się poleceniem [bun --version], a jeśli jest niższa niż 1.4.4, trzeba ją zaktualizować.
W systemach RPM wersję pakietu libbrotli sprawdza się poleceniem [rpm -q libbrotli]. Numery wersji pakietów nadaje dystrybucja, więc nie porównuje się ich bezpośrednio z numerami wydań Google, tylko z wersją, którą komunikat bezpieczeństwa dystrybucji wyda dla tego numeru CVE.
rpm -q libbrotlilibbrotli-1.0.x-x.el9_x.x86_64To, czy dystrybucja wydała komunikat, sprawdza się poleceniem [dnf updateinfo info --cve CVE-2026-111497]. Pusty wynik oznacza, że w tym repozytorium nie ma jeszcze komunikatu dla tej CVE.
dnf updateinfo info --cve CVE-2026-111497Pakiet, który przejmuje tylko poprawkę bez zmiany numeru wersji, zostawia numer CVE w dzienniku zmian. Dlatego sprawdza się to poleceniem [rpm -q --changelog libbrotli | grep CVE-2026-111497]; jeśli pojawi się wiersz, pakiet ma łatkę.
rpm -q --changelog libbrotli | grep CVE-2026-111497Według stanu na 23 października pakiety libbrotli w Debianie, Ubuntu, Red Hat i AlmaLinux nie mają jeszcze wersji z łatką dla tej CVE. Alpine wydał wersję z łatką tylko dla pakietu nodejs; źródłem są strony trackerów bezpieczeństwa poszczególnych dystrybucji. Na razie kolejność jest więc taka: obserwować komunikat bezpieczeństwa dystrybucji i zaktualizować, gdy się pojawi.
W Debianie i Ubuntu pakiet nazywa się libbrotli1, a te same punkty sprawdzono na serwerze testowym Debian 12 u dostawcy hostingu zarządzanego. Również na tym serwerze libcurl linkowała libbrotlidec, a rozszerzenie curl w PHP obsługiwało brotli. Dziennik zmian wyświetla się poleceniem [zcat /usr/share/doc/libbrotli1/changelog.Debian.gz | grep CVE-2026-111497].
dpkg -l libbrotli1 | tail -1ii libbrotli1:amd64 1.1.0-2+0~20240305.8+debian12~1.gbp62df71 amd64 library implementing brotli encoder and decoder (shared libraries)ldd /usr/lib/x86_64-linux-gnu/libcurl.so.4 | grep brotli libbrotlidec.so.1 => /lib/x86_64-linux-gnu/libbrotlidec.so.1 (0x00007fa2baac6000)
libbrotlicommon.so.1 => /lib/x86_64-linux-gnu/libbrotlicommon.so.1 (0x00007fa2b9cb9000)4️⃣ Aktualizacja Node.js: przejście na wersję z łatką i ponowne sprawdzenie
W hostingu zarządzanym i współdzielonym pakiety serwera aktualizuje dostawca, więc to, czy Node.js i libbrotli mają łatkę, sprawdza się w komunikatach dostawcy lub przez pomoc techniczną. Przy samodzielnie prowadzonym serwerze Node.js aktualizuje się do wersji z łatką z komunikatu nodejs.org, a Bun poleceniem [bun upgrade] do wersji 1.4.4 lub nowszej.
Node.js zainstalowany ze strumienia modułów dystrybucji aktualizuje się poleceniem w rodzaju [dnf upgrade nodejs], gdy wersja z łatką trafi do tego repozytorium. Na serwerze, na którym Node.js pobrano i zainstalowano bezpośrednio z nodejs.org, zastępuje się go wersją z łatką z tego samego miejsca. Linie bez wsparcia nie dostają wersji z łatką, więc trzeba przejść na wspieraną linię: 22, 24 lub 26.
Node.js Selector w hostingu współdzielonym (CloudLinux alt-nodejs) ma własną kopię brotli, więc aktualizacja systemowej libbrotli go nie naprawia. Według komunikatu CloudLinux nowe binarium działa dopiero po przejściu na pakiet udostępniony przez dostawcę i ponownym uruchomieniu aplikacji. Ten sam komunikat podaje, że w wersjach bez wsparcia wartość [node --version] się nie zmienia, więc sprawdza się numer release pakietu.
Pakiet libbrotli aktualizuje się, gdy pojawi się komunikat bezpieczeństwa dystrybucji, a potem uruchamia ponownie procesy, które mają bibliotekę w pamięci, aby przeszły na nową bibliotekę. Biblioteki współdzielone są wczytywane przy starcie procesu, więc na serwerze WordPress restart php-fpm należy do tego samego kroku.
Po aktualizacji sprawdza się, czy [node -v] pokazuje wersję z łatką lub nowszą, a [rpm -q libbrotli] wersję z komunikatu dystrybucji. To, czy numer CVE trafił do dziennika zmian, sprawdza się poleceniem [rpm -q --changelog libbrotli | grep CVE-2026-111497]. Po restarcie php-fpm warto sprawdzić też stan usługi i odpowiedź witryny.
systemctl is-active php-fpmactivecurl -s -o /dev/null -w '%{http_code}\n' 'https://example.com/?r=1'2005️⃣ Tymczasowe zabezpieczenie przed łatką: wyłączenie rozpakowywania br w zdalnych żądaniach WordPressa
Strona CVE Red Hat zaleca takie skonfigurowanie aplikacji, aby nie akceptowały kompresji br z niezaufanych źródeł albo nie rozpakowywały jej automatycznie.
Drogę po stronie WordPressa można zablokować już teraz, jeszcze przed wydaniem pakietów z łatką. WordPress udostępnia hook [http_api_curl], który przy każdym zdalnym żądaniu przekazuje uchwyt curl. Ustawienie w nim [CURLOPT_ENCODING] na null wyłącza automatyczne rozpakowywanie w libcurl.
Umieszczenie poniższego pliku w folderze [wp-content/mu-plugins/] obejmuje wszystkie zdalne żądania; nazwa pliku jest przykładowa. Aby to cofnąć, wystarczy usunąć ten plik.
<?php
// no-brotli-decode.php — WordPress zdalny w żądaniach libcurl Automatyczny rozpakowywanie Wyłączone(libbrotli łatka wersja wyjdzie do)
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'http_api_curl', function ( $handle ) {
curl_setopt( $handle, CURLOPT_ENCODING, null );
}, 99 );Łatwo pomyśleć, że ograniczenie [CURLOPT_ENCODING] do [gzip, deflate] wyklucza br. Ta wartość usuwa br tylko z nagłówka Accept-Encoding żądania; jeśli drugi serwer odpowie w br, libcurl i tak rozpakuje to, co otrzyma. Na witrynie testowej pobrano httpbin.org/brotli z tą samą wartością i odpowiedź z Content-Encoding br została rozpakowana i dała się przeczytać.
Według dokumentacji curl jedyną wartością wyłączającą automatyczne rozpakowywanie jest null; przy null nagłówek Accept-Encoding nie jest wysyłany, a odebrana treść nie jest rozpakowywana. Odpowiedzi gzip i deflate rozpakowuje sama biblioteka Requests w WordPressie i pozostają czytelne; nierozpakowane zostają tylko odpowiedzi br.
Ten hook WordPress podpiął pod punkt [curl.before_send], który Requests wywołuje tuż przed uruchomieniem curl. Jest wywoływany w wierszu 174 pliku Curl.php, a curl wykonuje się w wierszu 205, więc pusty ciąg ustawiony przez rdzeń w wierszu 110 zostaje w międzyczasie nadpisany.
To, czy zmiana działa, sprawdza się poniższym plikiem testowym. httpbin.org to adres testowy, który zwraca nagłówki odebranego żądania, a także odpowiedzi skompresowane br i gzip, więc od razu widać, co WordPress wysyła i co rozpakowuje.
<?php
// br-check.php — WordPress zdalny żądanie jaki kompresja wysyła czy rozpakowuje Sprawdź(httpbin.org testowy Adres)
$h = json_decode( wp_remote_retrieve_body( wp_remote_get( 'https://httpbin.org/headers' ) ), true );
echo 'Accept-Encoding: ', isset( $h['headers']['Accept-Encoding'] ) ? $h['headers']['Accept-Encoding'] : '(none)', PHP_EOL;
foreach ( array( 'brotli', 'gzip' ) as $p ) {
$r = wp_remote_get( 'https://httpbin.org/' . $p );
$readable = strpos( wp_remote_retrieve_body( $r ), ': true' ) !== false;
echo $p, ': Content-Encoding=', wp_remote_retrieve_header( $r, 'content-encoding' ), ' / ', strlen( wp_remote_retrieve_body( $r ) ), ' bytes / ', ( $readable ? 'readable' : 'not decoded' ), PHP_EOL;
}Przed dodaniem pliku Accept-Encoding zawiera br, a odpowiedź br zostaje rozpakowana i jest czytelna.
wp eval-file br-check.phpAccept-Encoding: deflate, gzip, br
brotli: Content-Encoding=br / 316 bytes / readable
gzip: Content-Encoding=gzip / 317 bytes / readablePo dodaniu pliku nagłówek Accept-Encoding znika, odpowiedź br pozostaje nierozpakowana, a odpowiedź gzip nadal jest czytelna.
wp eval-file br-check.phpAccept-Encoding: (none)
brotli: Content-Encoding=br / 199 bytes / not decoded
gzip: Content-Encoding=gzip / 272 bytes / readablePo aktualizacji do pakietu libbrotli z łatką z dystrybucji i restarcie php-fpm usuwa się ten plik, aby przywrócić pierwotny stan.
🔢 FAQ & polecane treści
Bezpieczeństwo
Luka w OpenSSH CVE-2024-6387
Jak tymczasowo zablokowano błąd OpenSSH, a potem wgrano łatkę i sprawdzono efekt.
Bezpieczeństwo
Bezpieczeństwo WP: zabezpieczenie wp-admin
Ustawienia, które chronią panel administracyjny przez zmianę adresu logowania, ograniczenie prób logowania i blokadę XML-RPC.
Serwer
Aktualizacja Alma Linux: 9.4 → 9.8
Kroki aktualizacji AlmaLinux 9.4 do 9.8 i punkty do sprawdzenia.
ℹ️ Informacja o linkach partnerskich
Treści tej witryny zawierają linki partnerskie. Jeśli odwiedzający kupi produkt lub usługę przez taki link, witryna otrzymuje od sprzedawcy prowizję. Kwota płacona przy tym przez kupującego (podczas promocji spada↓) nie wzrasta. Podane ceny, rabaty i dostępność odnoszą się do chwili pisania i mogą różnić się od stanu faktycznego, dlatego przed zakupem sprawdź je u sprzedawcy. Produkty wybieramy i oceniamy według własnych kryteriów, a prowizja nie wpływa ani na kolejność prezentacji, ani na treść ocen.