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

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 от Google | 1.0.3 ~ 1.2.0 | 1.0.10 · 1.1.1 · 1.2.1 | Запись NVD · релизы google/brotli (2026-10-21) |
| Bun | Brotli в списке исправлений безопасности объявления о версии 1.4.4 | 1.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
libsouplibcurl требует 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 BROTLIBROTLI => YesВерсия libcurl, которую использует WordPress, видна в строке [Версия cURL], если в админ-панели в разделе [Инструменты › Здоровье сайта] на вкладке [Информация] развернуть блок [Сервер]. Поддержку brotli эта строка не показывает, поэтому её проверяют командами на сервере, как выше.
Строка [Версия 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.php110: curl_setopt($this->handle, CURLOPT_ENCODING, '');
212: curl_setopt($this->handle, CURLOPT_ENCODING, 'none');Строка 110 задаёт значение по умолчанию для всех запросов, а строка 212 выполняется, только когда запрос, который не удалось распаковать, отправляется повторно без сжатия. Напрямую извне до этого места не добраться: сначала нужно захватить сервер, от которого WordPress получает ответы, — например, при проверке обновлений ядра и плагинов, в oEmbed или RSS-лентах.
Ход атаки начинается со сжатых 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 -vv24.xx.xИспользует ли Node.js системную libbrotli или встроенный декодер, проверяют через [ldd]. Если в выводе есть строка libbrotlidec, сборка использует общую библиотеку; если результат 0, как ниже, декодер встроен и ошибку исправит только обновление самого Node.js.
ldd $(command -v node) | grep -c brotli0Для Bun проверьте версию командой [bun --version] и обновите, если она ниже 1.4.4.
В системах на основе RPM версию пакета libbrotli смотрят командой [rpm -q libbrotli]. Номера версий пакетов назначает дистрибутив, поэтому их сравнивают не с номерами релизов Google напрямую, а с версией, которую бюллетень безопасности дистрибутива выпустит для этого номера CVE.
rpm -q libbrotlilibbrotli-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 -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️⃣ Обновление 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-fpmactivecurl -s -o /dev/null -w '%{http_code}\n' 'https://example.com/?r=1'2005️⃣ Временная мера до исправления: отключение распаковки 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.phpAccept-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.phpAccept-Encoding: (none)
brotli: Content-Encoding=br / 199 bytes / not decoded
gzip: Content-Encoding=gzip / 272 bytes / readableКогда пакет libbrotli из дистрибутива будет обновлён до исправленного и php-fpm перезапущен, удалите этот файл, чтобы вернуть исходную настройку.
🔢 FAQ & рекомендуемые материалы
Безопасность
Уязвимость OpenSSH CVE-2024-6387
Как ошибка OpenSSH была временно заблокирована, а затем установлен патч и проведена проверка.
Безопасность
Безопасность WP: защита wp-admin
Настройки, которые защищают панель администратора: смена адреса входа, ограничение попыток входа и блокировка XML-RPC.
Сервер
Обновление Alma Linux: 9.4 → 9.8
Шаги обновления AlmaLinux 9.4 до 9.8 и что проверить.
ℹ️ О партнёрских ссылках
Материалы этого сайта содержат партнёрские ссылки. Если посетитель покупает товар или услугу по такой ссылке, сайт получает комиссию от продавца. При этом сумма, которую платит покупатель (при акционных скидках сумма снижается ↓), не увеличивается. Указанные цены, скидки и наличие актуальны на момент написания и могут отличаться, поэтому перед покупкой уточняйте их у продавца. Товары выбираются и оцениваются по нашим собственным критериям, а комиссия не влияет ни на порядок, ни на содержание обзоров.