Назад

2026/10/26

HTTP2Shell (CVE-2026-111497): обновление бинарного файла не исправляет уже запущенные процессы

Уязвимость Brotli в основе HTTP2Shell затрагивает Node.js, Bun и Python. Как найти реальные риски в запущенных процессах и безопасно перезапустить ИИ-агентов.

21 октября 2026 года Node.js выпустил исправления для CVE-2026-111497 — записи за границы буфера в декодере Brotli от Google. В течение нескольких дней последовали бюллетени для Bun, Python-пакетов brotli и brotlicffi, Envoy и Istio, а профильные СМИ окрестили уязвимость Node2Shell, а затем HTTP2Shell. Несмотря на название, это не ошибка в протоколе HTTP/2. Это ошибка распаковки, и она важна везде, где программа декодирует данные Brotli, на которые может повлиять атакующий.

В статье разобрано, что подтверждено, что лишь возможно и как проверить собственные системы. Кроме того, мы делимся уроком из собственного реагирования: установка исправленного бинарного файла не исправляет процесс, который всё ещё выполняет старый. В нашей среде долго работающие сессии Claude Code сохраняли старые встроенные среды выполнения уже после того, как установленный Claude Code был обновлён.

Главное

  • Уязвимы: upstream-версии Brotli с 1.0.3 по 1.2.0. Исправлены: 1.0.10, 1.1.1 и 1.2.1. Продуктам, которые поставляют Brotli в своём составе, нужен собственный исправленный релиз.
  • Для эксплуатации нужен реальный путь: данные Brotli под влиянием атакующего должны попасть в уязвимый декодер. Название продукта или сетевой доступ сами по себе ничего не доказывают.
  • Проверяйте запущенные процессы, а не только установленные пакеты. Процесс сохраняет среду выполнения, с которой стартовал, до перезапуска.
  • Перезапускайте ИИ-агентов с состоянием по одному, предварительно передав их события, согласования и рабочее состояние.

1. В чём суть уязвимости

Встретив некорректное переключение блока, декодер сообщал «нужно больше входных данных» вместо окончательной ошибки формата. Потоковый вызывающий код продолжал подавать байты, а декодер дописывал их во внутренний восьмибайтовый буфер, выходя за его границу. Согласно бюллетеню Node.js, атакующий, способный передать данные Brotli, которые приложение распаковывает через node:zlib, «может повредить память процесса, что может привести к аварийному завершению, раскрытию памяти процесса или удалённому выполнению кода».

Оценки критичности зависят от источника. Node.js оценивает уязвимость как High. Бюллетень GitHub для Python-пакетов и бюллетень Envoy — как Critical, оценка CVSS 4.0 — 9.2. Ubuntu сейчас присваивает ей приоритет Medium. По состоянию на 26 октября оценка CISA в записи CVE указывает эксплуатацию как «none», и CVE отсутствует в каталоге Known Exploited Vulnerabilities CISA. Тем не менее бюллетень Node.js называет удалённое выполнение кода возможным последствием, поэтому отсутствие сообщений об атаках — не повод ждать.

2. Предпосылки: когда вы действительно уязвимы

Должны выполняться все три условия:

  1. В процессе загружен уязвимый декодер: встроенная или системная библиотека Brotli из затронутого диапазона без исправления или бэкпорта.
  2. Что-то его вызывает: явная распаковка или автоматическое декодирование Content-Encoding: br.
  3. На входные данные может влиять атакующий: ответы с произвольных URL, загружаемые файлы, тела входящих запросов или открытый HTTP-трафик во враждебной сети.

Автоматическое декодирование — путь, который команды упускают чаще всего. В бюллетене Bun отмечено, что fetch по умолчанию отправляет Accept-Encoding: br и декодирует ответ, так что путь есть у любой программы на Bun, загружающей недоверенные URL. В Python после установки brotli или brotlicffi библиотека urllib3 автоматически декодирует ответы с Content-Encoding: br, а requests тоже объявляет поддержку br в своих запросах. Это происходит, даже если ваш собственный код нигде не упоминает Brotli. Envoy и Istio уязвимы, только если настроен декомпрессор Brotli.

3. Подтверждённая уязвимость и лишь возможная

Состояние на 26 октября 2026 года. Сверяйтесь с бюллетенями по ссылкам: ситуация быстро меняется.

КомпонентСтатусИсправлено в
Upstream Brotli (C)Подтверждено: с 1.0.3 по 1.2.01.0.10, 1.1.1, 1.2.1
Node.js 22, 24, 26Подтверждено22.23.4, 24.22.1, 26.12.1
Node.js 20 и более ранние (окончание поддержки)Не оценивалось; Node.js всегда считает ветки с окончившейся поддержкой уязвимымиПерейти на поддерживаемую ветку
Bun (включая скомпилированные исполняемые файлы)Подтверждено: с 1.0.21 по 1.4.31.4.4, а также пересобрать скомпилированные приложения
Python brotli / brotlicffiПодтверждено: начиная с 1.0.31.2.1 / 1.2.1.0
Envoy (настроен декомпрессор Brotli)Подтверждено1.40.0, 1.39.3, 1.38.6, 1.37.8 (1.36.12 в неподдерживаемой ветке 1.36)
Istio (включена распаковка Brotli)Подтверждено1.31.2, 1.30.6, 1.29.9 (последний релиз 1.29)
Пакет brotli в UbuntuТребует оценки (все релизы)Исправленного пакета пока нет
Пакет nodejs в UbuntuУязвим в 20.04 (встроенный Brotli). В 22.04 и новее использует системный Brotli, поэтому статус как в строке вышеСм. трекер Ubuntu
Пакет brotli в DebianИсправлен в bookworm и trixie; в forky ещё нет1.0.9-2+deb12u1, 1.1.0-2+deb13u1
Chrome, Electron и приложения на ElectronНеизвестно: заявлений производителя не найденоСледить за примечаниями к релизам производителя

О последней строке: Chrome и Electron поставляют собственные копии Brotli, а приложение на Electron также встраивает среду выполнения Node.js. На момент написания новейшие стабильные релизы Electron (45.0.0, 44.9.3 и 43.8.3, все от 21 октября) содержат Node.js 24.22.0, а не 24.22.1, и в их примечаниях эта CVE не упоминается. Предварительные релизы Electron от 23 октября уже содержат Node.js 24.22.1, так что стабильное исправление выглядит вероятным, но пока не вышло. Поэтому настольное приложение на Electron возможно уязвимо, но это не подтверждено. То же относится к чат-клиентам и настольным ИИ-инструментам: прежде чем называть их уязвимыми или безопасными, проверьте заявление каждого производителя.

Ещё одна ловушка: заявленная версия библиотеки не доказывает ни того, ни другого. Исправленный Node.js 22.23.4 по-прежнему сообщает process.versions.brotli как 1.1.0, потому что исправление было перенесено бэкпортом. Дистрибутивы поступают так же. Ориентируйтесь на исправленную версию продукта или статус безопасности в дистрибутиве, а не на строку версии Brotli.

4. Что мы обнаружили в собственной среде

Прежде чем что-либо менять, мы провели инвентаризацию в режиме только чтения. Ниже наши наблюдения в анонимизированном виде; подтверждённые выводы отделены от рисков, которые мы не смогли проверить.

Подтверждённые наблюдения

  • Установленный Claude Code уже был обновлён, но десяток долго работающих сессий всё ещё выполняли старые сборки, запущенные несколькими неделями ранее. Их ни разу не перезапускали.
  • При первом подсчёте мы пропустили три из них. Они работали под другим вспомогательным процессом, поэтому поиск по командной строке их не находил. Поиск по исполняемому файлу их обнаружил.
  • Файлы старых сборок программа обновления уже удалила с диска, поэтому версию встроенного Bun в этих процессах прочитать было невозможно.
  • Оставшаяся установка старой сборки Claude Code через менеджер пакетов содержала Bun 1.4.0. В обычной работе её ничто не использовало, но резервный путь мог бы. Мы удалили её, убедившись, что от неё ничего не зависит.
  • На одном из серверов всё ещё была установлена неиспользуемая среда Node.js с окончившейся поддержкой. Мы удалили её тем же способом.
  • Текущая сборка Claude Code, до которой мы обновились (2.1.310), содержит Bun 1.4.4. Мы подтвердили это, исследовав бинарный файл. В журнале изменений Anthropic эта CVE не упоминается, поэтому проверьте свою сборку таким же образом.

Не проверено, считается риском

  • Были ли старые сессии действительно уязвимы. Версию Bun в них прочитать не удалось. Исправленный Bun 1.4.4 появился лишь около 22 октября, позже этих сборок, поэтому мы сочли их уязвимыми.
  • Мог ли атакующий добраться до декодера через эти сессии, например через загрузку вредоносного URL. Такой путь правдоподобен с учётом документированного поведения fetch в Bun, но мы его не доказали.
  • Затронуто ли другое ПО со встроенной собственной копией Brotli. Если заявления производителя нет, мы отслеживаем продукт по его релизам, а не считаем его заранее безопасным или уязвимым.

Признаков эксплуатации мы не обнаружили. Но и это мы не сочли доказательством безопасности: решением было устранить уязвимость, а не отговориться от неё.

5. Чек-лист обнаружения и устранения

Найдите зависимости

Ищите Brotli там, где он встроен, а не только там, где он установлен как пакет. Проверьте SBOM и образы контейнеров на наличие Node.js, Bun, Python-пакетов, Envoy и статически слинкованного Brotli. Проверьте транзитивные зависимости Python: Brotli часто попадает туда как необязательное дополнение.

# Node.js: compare with 22.23.4 / 24.22.1 / 26.12.1
node -p process.version
# Python packages: run once per interpreter and virtual environment
python -m pip list 2>/dev/null | grep -iE '^(brotli|brotlicffi) '
pipdeptree -r -p brotli          # what pulled it in
# Debian / Ubuntu system library (then check the distro tracker)
dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\n' 'libbrotli*'
# Container images, with an SBOM scanner such as Syft
syft <image> | grep -iE 'brotli|node|bun'

Найдите встроенные среды выполнения

Программы, собранные с помощью bun build --compile, содержат собственную копию Bun. Согласно бюллетеню Bun, они остаются уязвимыми, пока их не пересоберут с 1.4.4. Обновление Bun на сборочной машине не меняет уже развёрнутые бинарные файлы.

# The Bun version inside a compiled executable
grep -aoE 'Bun v1\.[0-9]+\.[0-9]+' /path/to/binary | sort -u

Найдите запущенные процессы

Проверяйте то, что запущено, а не то, что установлено. В Linux у процесса, исполняемый файл которого был заменён или удалён, отображается (deleted). Это серьёзный признак того, что он всё ещё выполняет старую сборку.

# Processes still running a deleted (replaced) executable (run as root to see all users)
for p in /proc/[0-9]*; do
  e=$(readlink "$p/exe" 2>/dev/null) || continue
  case "$e" in *"(deleted)") echo "${p#/proc/} $e";; esac
done
# Processes older than the fix
pgrep -af 'node|bun|claude'      # then: ps -o pid,lstart,args -p <pid>

В macOS команда lsof -p <pid> показывает исполняемый файл, загруженный процессом. Сопоставляйте по исполняемому файлу, а не по имени процесса, иначе пропустите вспомогательные процессы и фоновые обработчики.

Исправьте, пересоберите, разверните заново

  • Обновитесь до исправленных версий из таблицы. Если существует бэкпорт, используйте исправленный пакет своего дистрибутива.
  • Пересоберите образы контейнеров и скомпилированные исполняемые файлы, затем разверните их заново. Исправленный базовый образ ничего не даёт образу, который вы не пересобрали.
  • Удалите неиспользуемые среды выполнения и остатки установок, убедившись, что от них не зависят ни сервисы, ни задания, ни скрипты. Сохраните возможность отката.
  • Если исправления ещё нет, отключите декодирование Brotli для недоверенных входных данных там, где это безопасно. Для Bun работает fetch(url, { decompress: false }) на уровне отдельного запроса. Назначьте каждой мере ответственного и дату окончания.

Перезапустите и проверьте

После перезапуска проверьте фактический исполняемый файл и версию среды выполнения нового процесса. Затем проверьте бизнес-результат: очереди разобраны, плановые задания выполнены успешно, оповещения мониторинга приходят. Успешная команда обновления доказывает лишь то, что команда выполнилась.

6. Безопасный перезапуск постоянно работающих ИИ-агентов

Долго работающие ИИ-агенты хранят состояние: незавершённые вызовы инструментов, согласования, сообщения в очереди и взятые в работу задачи. Одновременный перезапуск всех агентов может это состояние уничтожить. У нас сработала следующая последовательность:

  1. Проинвентаризируйте каждую сессию: версия исполняемого файла, состояние (простаивает, ожидает или занята), последняя активность, незавершённая работа, согласования, которых она ждёт или которые удерживает, неподтверждённые события и способ их восстановления.
  2. Вынесите согласования из памяти. Согласование, существующее только в диалоге сессии, при перезапуске теряется. Перед перезапуском зафиксируйте каждое ожидающее согласование (что одобрено, кем и для какого именно изменения) в надёжном месте, например в тикете или pull request. После перезапуска убедитесь, что сессия по-прежнему его видит. При сомнениях запросите согласование заново, а не предполагайте его наличие.
  3. Сначала докажите работоспособность замены. Убедитесь, что сессии на исправленной сборке уже работают и что запущен наблюдатель событий, не зависящий от старых сессий, — чтобы ни одно событие не зависело от перезапускаемой сессии.
  4. Сначала перезапускайте простаивающие сессии, по одной. После каждой проверьте, что старый процесс завершён, новый выполняет исправленный исполняемый файл, история диалога цела, а удалённое подключение, если оно было, восстановлено. Только затем переходите к следующей.
  5. Самого загруженного владельца перезапускайте последним, между задачами. Пусть он напишет заметку для передачи дел; останавливайте его наблюдатель только при нуле незавершённых и нуле неподтверждённых событий, перезапустите, затем убедитесь, что наблюдатель продолжил с сохранённой позиции без пропусков.
  6. Ожидайте накопившиеся входные данные при переподключении. Одна из наших перезапущенных сессий получила сообщение, ждавшее десять дней, и выполнила его. Перед перезапуском проверьте входящие каждой сессии и предупредите её владельца.
  7. Остановитесь и эскалируйте, если какой-либо шаг не позволяет подтвердить, что работа восстановлена. Не прибегайте к принудительному завершению всего подряд.

Определите, что значит «готово», до начала работ. Готово — это когда ни один запущенный процесс не работает на уязвимой сборке, каждое исключение задокументировано с ответственным и датой повторной проверки, а бизнес-проверки проходят. «Пакет обновлён» — это ещё не готово.

Источники