Уязвимость Node.js: патч Node2Shell и проверка версии Node.js (WordPress)

Уведомление о партнёрских ссылках v3.1
Уведомление о партнёрских ссылках v3.2 (мобильная версия)
Тестовая среда Семейство RHELAlmaLinux 9.8Nginx + Apache · WordPress 7.1.3Проверено 2026-10-23

Node2Shell — это прозвище CVE-2026-111497, уязвимости Node.js, исправление для которой вышло 21 октября 2026 года; официальное название описывает ошибку записи за пределами буфера в декодере Brotli от Google. Поскольку распаковка специально подготовленных данных Brotli может привести к удалённому выполнению кода, первым делом проверьте версии Node.js, Bun и libbrotli на сервере и обновите их до исправленных версий.

На сервере Nginx + Apache с WordPress 7.1.3 были проверены пакеты, использующие декодер Brotli, и путь, по которому удалённые запросы WordPress проходят через libbrotli.

1️⃣ Уязвимость Node.js Node2Shell: как устроена ошибка и что она затрагивает

Название Node2Shell — прозвище, которое дала исследовательская команда компании Imperva, работающей в сфере безопасности, в статье от 22 октября. Заголовок бюллетеня на nodejs.org — [Brotli decoder out-of-bounds write permits remote code execution]. Ошибка находится в исходном коде декодера Google Brotli, встроенного в Node.js (c/dec/decode.c), поэтому исправлением кода приложения её не закрыть.

Согласно записи NVD, декодер Google Brotli версий с 1.0.3 по 1.2.0 при распаковке специально подготовленного сжатого потока записывает данные за концом буфера и повреждает соседнюю память состояния декодера. Через эту ошибку удалённый злоумышленник без входа в систему может прочитать содержимое памяти сервера, остановить работу сервиса и выполнить произвольный код.

Оценок опасности две. Бюллетень на nodejs.org присвоил уровень High, а Google, выпустившая CVE, поставила 9,2 балла (Critical) по CVSS 4.0. По состоянию на 23 октября NVD ещё не завершила собственный анализ, поэтому оценки NVD пока нет. Типы слабостей — непроверяемое возвращаемое значение (CWE-252) и запись за пределами буфера (CWE-787).

Согласно бюллетеню безопасности на nodejs.org, затронуты все поддерживаемые ветки Node.js 22.x, 24.x и 26.x, а исправленные версии — 22.23.4, 24.22.1 и 26.12.1. В том же бюллетене сказано, что ветки, поддержка которых закончилась, при каждом бюллетене безопасности всегда считаются затронутыми.

Затронута любая программа, использующая тот же декодер, в том числе за пределами Node.js. 21 октября Google выпустила три версии библиотеки Brotli, исправляющие ту же CVE: 1.0.10, 1.1.1 и 1.2.1. Bun 22 октября включил Brotli в список исправлений безопасности в объявлении о версии 1.4.4, а в issue репозитория сказано, что эта версия содержит исправление с обновлением brotli.

приёмникЗатронутые версииИсправленная версияИсточник
Node.jsВсе поддерживаемые ветки 22.x · 24.x · 26.x · ветки без поддержки22.23.4 · 24.22.1 · 26.12.1Бюллетень безопасности nodejs.org (2026-10-21)
Библиотека Brotli от Google1.0.3 ~ 1.2.01.0.10 · 1.1.1 · 1.2.1Запись NVD · релизы google/brotli (2026-10-21)
BunBrotli в списке исправлений безопасности объявления о версии 1.4.41.4.4Объявление Bun (2026-10-22)
Пакет libbrotli дистрибутиваПакеты, собранные на основе Brotli с 1.0.3 по 1.2.0Версия из бюллетеня безопасности дистрибутива (на 2026-10-23 ещё нет)Страница трекера безопасности каждого дистрибутива

Сам Node.js не распаковывает полученные данные Brotli по собственной инициативе. Согласно бюллетеню безопасности хостинговой компании Vercel, уязвимые места — там, где приложение или библиотека распаковывает сжатые данные, пришедшие извне. Пример — встроенный fetch(), когда он получает ответ br, и Express 5 или body-parser 2, когда они распаковывают тело запроса br; стандартный HTTP-сервер, который только принимает данные, ничего не распаковывает.

По состоянию на 23 октября реальная эксплуатация не подтверждена. Этой CVE нет в каталоге известных эксплуатируемых уязвимостей CISA (KEV, версия от 21 октября). Стадия эксплуатации, которую CISA указывает в записи CVE, 23 октября сменилась на [публичное доказательство концепции (PoC)].

От того, где находится декодер, зависит, что нужно делать. Официальные бинарные файлы Node.js содержат декодер внутри исполняемого файла, но есть и сборки, использующие системную libbrotli, например пакет nodejs в Debian. Дистрибутивы Linux устанавливают тот же исходный код как пакет libbrotli, которым совместно пользуются программы вроде curl.

2️⃣ Сайты на WordPress и PHP тоже затронуты: путь, по которому удалённые запросы распаковывают ответы через libbrotli

Даже на сервере WordPress без Node.js тот же декодер установлен в виде пакета libbrotli. По состоянию на 23 октября официальных заявлений об этой CVE со стороны WordPress, curl и PHP нет, и в официальном списке уязвимостей curl её тоже нет. Поэтому в этом разделе подтверждается только устройство — по коду ядра, документации curl и замерам на тестовом сервере.

На проверенном сервере Nginx + Apache библиотеку декодера (libbrotlidec) требовали пять пакетов, и связан с WordPress из них libcurl.

rpm -q --whatrequires 'libbrotlidec.so.1()(64bit)' | sed 's/-[0-9].*//'
freetype
brotli-devel
libjxl
libcurl
libsoup

libcurl требует libbrotli, чтобы распаковывать сжатые HTTP-ответы. Согласно официальной документации curl, libcurl, собранная с библиотекой brotli, автоматически распаковывает ответы с Content-Encoding br. Эту libcurl напрямую вызывает расширение curl в 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 BROTLI
BROTLI => Yes

Версия libcurl, которую использует WordPress, видна в строке [Версия cURL], если в админ-панели в разделе [Инструменты › Здоровье сайта] на вкладке [Информация] развернуть блок [Сервер]. Поддержку brotli эта строка не показывает, поэтому её проверяют командами на сервере, как выше.

Экран вкладки «Информация» раздела «Здоровье сайта»: левое меню Инструменты › Здоровье сайта и строки «Серверная архитектура», «ПО веб-сервера», «Версия PHP», «PHP SAPI» и «Версия cURL» в блоке «Сервер» — проверка в строке «Версия cURL» версии libcurl, пути уязвимости Node.js Node2Shell на стороне WordPress (WordPress 7.1.3)

Строка [Версия cURL] на снимке показывает версию libcurl вместе с библиотекой SSL, с которой она собрана. Согласно документации curl, br поддерживается начиная с 7.57.0, поэтому если значение ниже, ответы br не распаковываются. Если оно такое же или выше, командами на сервере проверьте, вошла ли brotli в сборку.

Библиотека Requests в ядре WordPress при запросах к внешним адресам устанавливает значение [CURLOPT_ENCODING] дескриптора curl в пустую строку. Согласно документации curl, пустая строка означает приём всех методов сжатия, которые поддерживает эта libcurl. Поэтому если сервер на другой стороне пришлёт ответ, сжатый br, его распакует libbrotli сервера WordPress.

grep -n CURLOPT_ENCODING wp-includes/Requests/src/Transport/Curl.php
110:            curl_setopt($this->handle, CURLOPT_ENCODING, '');
212:            curl_setopt($this->handle, CURLOPT_ENCODING, 'none');

Строка 110 задаёт значение по умолчанию для всех запросов, а строка 212 выполняется, только когда запрос, который не удалось распаковать, отправляется повторно без сжатия. Напрямую извне до этого места не добраться: сначала нужно захватить сервер, от которого WordPress получает ответы, — например, при проверке обновлений ядра и плагинов, в oEmbed или RSS-лентах.

🧩 Ход атаки Node2Shell
Поясняющая схема
①Сжатые данные извнеТело запроса, сжатое br, или ответ другого сервера
②Приложение распаковывает их декодером BrotliNode.js fetch() · Express 5 · Bun fetch() · WordPress через libcurl → libbrotli
③Ошибка декодера BrotliПодготовленный поток пишет за концом буфераCVE-2026-111497
④Выполнение кода на сервереУтечка памяти · остановка сервиса · захват сервера
✅ В исправленных версиях этот путь закрытNode.js 22.23.4 · 24.22.1 · 26.12.1Bun 1.4.4Brotli 1.0.10 · 1.1.1 · 1.2.1

Ход атаки начинается со сжатых br данных, пришедших извне. Ошибка срабатывает на шаге, когда fetch() в Node.js, Express 5, fetch() в Bun или libbrotli, вызванная libcurl в WordPress, распаковывает эти данные. Итог — выполнение кода на сервере. Исправленные версии перекрывают этот путь, поэтому нужно заменить декодер исправленной версией.

Модуль mod_brotli в Apache связывает только библиотеку кодировщика (libbrotlienc), которая сжимает ответы, поэтому конфигурация, включающая сжатие этим модулем, не лежит на пути ошибки декодера.

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️⃣ Проверка версии Node.js: есть ли на сервере Node.js, Bun и libbrotli и какие у них версии

Начать стоит с того, чтобы выяснить, что установлено на сервере. Для Node.js версию смотрят командой [node -v]; если значение ниже 22.23.4, 24.22.1 или 26.12.1 для своей ветки, версия уязвима.

Ваша средаКоманда проверкиЧто исправляется
Установщик с nodejs.org · nvm · официальный образ Docker[node -v]Сам Node.js (22.23.4 · 24.22.1 · 26.12.1)
nodejs из дистрибутива · в ldd есть строка libbrotli[ldd $(command -v node)]Пакет libbrotli
nodejs из дистрибутива · результат ldd 0[ldd $(command -v node)]Пакет nodejs (после бюллетеня безопасности дистрибутива)
Node.js Selector на виртуальном хостинге (CloudLinux alt-nodejs)Объявление провайдера · номер release пакетаПакет от провайдера + перезапуск приложения
Bun[bun --version]Bun 1.4.4 и новее
Без Node.js (только WordPress)Проверка libcurl через [ldd]Пакет libbrotli + перезапуск php-fpm · до этого — временная мера
Управляемый хостингОбъявление провайдера · запрос в поддержкуПакеты, которые обновляет провайдер

На ПК (Windows или macOS) переустановите Node.js установщиком исправленной версии с nodejs.org; в nvm посмотрите версию командой [nvm ls] и переустановите по номеру ветки, например [nvm install 24]. Теги 22, 24 и 26 официального образа node в Docker пересобраны с исправленными версиями 22 октября, поэтому скачайте образ заново, пересоберите и разверните. Бюллетень Vercel также рекомендует обновить до исправленных версий собственные образы контейнеров, среды CI и локальные среды разработки.

На сервере, где Node.js установлен из потока dnf module, [node -v] показывает версию из ветки этого потока; даже при той же ветке обновление нужно, если последние цифры не дотягивают до исправленной версии.

node -v
v24.xx.x

Использует ли Node.js системную libbrotli или встроенный декодер, проверяют через [ldd]. Если в выводе есть строка libbrotlidec, сборка использует общую библиотеку; если результат 0, как ниже, декодер встроен и ошибку исправит только обновление самого Node.js.

ldd $(command -v node) | grep -c brotli
0

Для Bun проверьте версию командой [bun --version] и обновите, если она ниже 1.4.4.

В системах на основе RPM версию пакета libbrotli смотрят командой [rpm -q libbrotli]. Номера версий пакетов назначает дистрибутив, поэтому их сравнивают не с номерами релизов Google напрямую, а с версией, которую бюллетень безопасности дистрибутива выпустит для этого номера CVE.

rpm -q libbrotli
libbrotli-1.0.x-x.el9_x.x86_64

Вышел ли бюллетень дистрибутива, проверяют командой [dnf updateinfo info --cve CVE-2026-111497]. Если вывод пустой, в этом репозитории ещё нет бюллетеня для этой CVE.

dnf updateinfo info --cve CVE-2026-111497

Пакет, в который перенесли только исправление без смены номера версии, оставляет номер CVE в журнале изменений. Поэтому проверка — [rpm -q --changelog libbrotli | grep CVE-2026-111497]; если появляется строка, пакет исправлен.

rpm -q --changelog libbrotli | grep CVE-2026-111497

По состоянию на 23 октября для пакетов libbrotli в Debian, Ubuntu, Red Hat и AlmaLinux исправленной версии по этой CVE ещё нет. В Alpine исправленная версия вышла только для пакета nodejs; источник — страницы трекеров безопасности дистрибутивов. Поэтому сейчас порядок такой: следить за выходом бюллетеня безопасности дистрибутива и обновляться, когда он выйдет.

В Debian и Ubuntu пакет называется libbrotli1; те же пункты были проверены на тестовом сервере Debian 12 у одного провайдера управляемого хостинга. На этом сервере libcurl тоже связывала libbrotlidec, а расширение curl в PHP поддерживало brotli. Журнал изменений смотрят командой [zcat /usr/share/doc/libbrotli1/changelog.Debian.gz | grep CVE-2026-111497].

dpkg -l libbrotli1 | tail -1
ii  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️⃣ Обновление Node.js: переход на исправленную версию и повторная проверка

На управляемом и виртуальном хостинге серверные пакеты обновляет провайдер, поэтому, исправлены ли Node.js и libbrotli, узнавайте из его объявлений или через поддержку. Если вы управляете сервером сами, обновите Node.js до исправленной версии из бюллетеня nodejs.org, а Bun — командой [bun upgrade] до 1.4.4 или новее.

Node.js, установленный из потока модулей дистрибутива, обновляют командой вида [dnf upgrade nodejs] после того, как исправленная версия появится в этом репозитории. На сервере, где Node.js скачан и установлен напрямую с nodejs.org, замените его исправленной версией оттуда же. Для веток без поддержки исправленных версий не будет, поэтому переходите на поддерживаемую ветку: 22, 24 или 26.

Node.js Selector на виртуальном хостинге (CloudLinux alt-nodejs) содержит собственную копию brotli, поэтому обновление системной libbrotli его не исправляет. Согласно объявлению CloudLinux, новый бинарный файл начинает работать только после перехода на пакет от провайдера и перезапуска приложения. В том же объявлении сказано, что у версий без поддержки значение [node --version] не меняется, поэтому проверять нужно по номеру release пакета.

Пакет libbrotli обновляют, как только выйдет бюллетень безопасности дистрибутива, а затем перезапускают процессы, которые держат библиотеку в памяти, — только тогда они переходят на новую библиотеку. Общие библиотеки читаются при запуске процесса, поэтому на сервере WordPress перезапуск php-fpm входит в тот же шаг.

После обновления проверьте, что [node -v] показывает исправленную версию или новее, а [rpm -q libbrotli] — версию из бюллетеня дистрибутива. Попал ли номер CVE в журнал изменений, проверяется командой [rpm -q --changelog libbrotli | grep CVE-2026-111497]. Если php-fpm перезапускался, проверьте также состояние службы и ответ сайта.

systemctl is-active php-fpm
active
curl -s -o /dev/null -w '%{http_code}\n' 'https://example.com/?r=1'
200

5️⃣ Временная мера до исправления: отключение распаковки br в удалённых запросах WordPress

Страница Red Hat об этой CVE рекомендует настроить приложения так, чтобы они не принимали сжатие br из ненадёжных источников или не распаковывали его автоматически.

Даже до выхода исправленных пакетов путь на стороне WordPress можно перекрыть уже сейчас. WordPress предоставляет хук [http_api_curl], который передаёт дескриптор curl при каждом удалённом запросе. Если там установить [CURLOPT_ENCODING] в null, автоматическая распаковка в libcurl отключится.

Если положить файл ниже в папку [wp-content/mu-plugins/], он применится ко всем удалённым запросам; имя файла приведено для примера. Чтобы откатить изменение, удалите этот файл.

<?php
// no-brotli-decode.php — WordPress удалённый в запросах libcurl Автоматически распаковка Выкл.(libbrotli патч версия выйдет пока не)
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}
add_action( 'http_api_curl', function ( $handle ) {
    curl_setopt( $handle, CURLOPT_ENCODING, null );
}, 99 );

Легко подумать, что если сузить [CURLOPT_ENCODING] до [gzip, deflate], br исчезнет. Это значение лишь убирает br из заголовка Accept-Encoding запроса, поэтому если сервер на другой стороне ответит в br, libcurl всё равно распакует полученное. На тестовом сайте при получении httpbin.org/brotli с тем же значением ответ с Content-Encoding br был распакован и читался как обычно.

Согласно документации curl, единственное значение, отключающее автоматическую распаковку, — null; при null заголовок Accept-Encoding не отправляется, а полученное тело не распаковывается. Ответы gzip и deflate распаковывает сама библиотека Requests в WordPress, и они остаются читаемыми; нераспакованными остаются только ответы br.

Этот хук WordPress подключил к точке [curl.before_send], которую Requests вызывает непосредственно перед запуском curl. Он вызывается в строке 174 файла Curl.php, а curl выполняется в строке 205, поэтому пустая строка, которую ядро установило в строке 110, перезаписывается между ними.

Применилась ли настройка, проверяют тестовым файлом ниже. httpbin.org — тестовый адрес, который возвращает полученные заголовки запроса, а также ответы, сжатые br и gzip, так что сразу видно, что WordPress отправляет и что распаковывает.

<?php
// br-check.php — WordPress удалённый запрос какое сжатие отправляет распаковывает ли Проверка(httpbin.org тестовый Адрес)
$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;
}

До добавления файла Accept-Encoding содержит br, а ответ br распаковывается и читается.

wp eval-file br-check.php
Accept-Encoding: deflate, gzip, br
brotli: Content-Encoding=br / 316 bytes / readable
gzip: Content-Encoding=gzip / 317 bytes / readable

После добавления файла заголовок Accept-Encoding пропадает, ответ br остаётся нераспакованным, а ответ gzip по-прежнему читается.

wp eval-file br-check.php
Accept-Encoding: (none)
brotli: Content-Encoding=br / 199 bytes / not decoded
gzip: Content-Encoding=gzip / 272 bytes / readable

Когда пакет libbrotli из дистрибутива будет обновлён до исправленного и php-fpm перезапущен, удалите этот файл, чтобы вернуть исходную настройку.

🔢 FAQ & рекомендуемые материалы

Автоматические обновления WordPress охватывают только ядро, плагины, темы и переводы; libbrotli и Node.js — пакеты, установленные на сервере, и их обновляет администратор сервера или хостинг-провайдер. Только после обновления и перезапуска php-fpm удалённые запросы WordPress начинают обрабатываться новой библиотекой.

Проходит ли плагин через WordPress HTTP API или напрямую вызывает функции curl, он использует одну и ту же libcurl. Исправлять нужно пакет libbrotli на сервере, а до выхода исправленной версии автоматическую распаковку удалённых запросов WordPress можно отключить настройкой из раздела 5️⃣. Согласно документации curl, код, который напрямую вызывает функции curl и не задаёт [CURLOPT_ENCODING], не распаковывает сжатые ответы автоматически. Код, который вызывает их напрямую и задаёт пустую строку, находится вне хука [http_api_curl], поэтому запросы такого плагина проверяют отдельно.

В записях CVE-2026-111497 в NVD и на cve.org (CNA Google) указаны место ошибки (c/dec/decode.c), затронутые версии и тип слабости. В примечаниях к релизам google/brotli v1.2.1, v1.1.1 и v1.0.10 отражено исправление. Подробности воспроизведения атаки в эту статью не включены.

ℹ️ О партнёрских ссылках
Материалы этого сайта содержат партнёрские ссылки. Если посетитель покупает товар или услугу по такой ссылке, сайт получает комиссию от продавца. При этом сумма, которую платит покупатель (при акционных скидках сумма снижается ↓), не увеличивается. Указанные цены, скидки и наличие актуальны на момент написания и могут отличаться, поэтому перед покупкой уточняйте их у продавца. Товары выбираются и оцениваются по нашим собственным критериям, а комиссия не влияет ни на порядок, ни на содержание обзоров.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Докажите, что вы человек: 10   +   9   =