Brotli CVE-2026-111497: обновление Node.js, Bun и Python
CVE-2026-111497 - ошибка повреждения памяти в декодере Brotli от Google, коде на C, который распаковывает данные Content-Encoding: br. Специально подготовленные сжатые данные пишут за конец буфера, что может уронить программу, привести к утечке памяти или выполнить код атакующего. Node.js, Bun, Python-пакеты Brotli и brotlicffi и Envoy несут каждый свою копию этого декодера, поэтому обновлять нужно каждый отдельно. В этом руководстве: когда приложение действительно уязвимо, как проверить сервер одной командой и как раскатить исправление, не пропустив ни одного контейнера.
Насколько это серьезно
- Google оценивает уязвимость в 9.2 (critical) по CVSS 4.0, Node.js - как high, Red Hat - в 8.1.
- Исследователи Imperva выполнили собственный код на непропатченном Node.js 24.22.0 на Linux ARM64 - и через полученный по fetch ответ, и через тело запроса в Express. Эксплойт не опубликован.
- В оценке CISA от 23 октября эксплуатация в реальных атаках не зафиксирована, надежного публичного proof of concept нет.
- Уязвимость работает в обе стороны: сервер, который распаковывает сжатые входные данные, может атаковать клиент, а клиент, который распаковывает сжатые ответы, - сервер.
Исправление влили в исходный код Brotli 27 февраля, но ни один релиз Brotli не содержал его до 21 октября, поэтому все пропатченные рантаймы вышли на одной неделе.
Уязвимы ли вы?
Под угрозой только код, который декодирует данные Brotli, контролируемые кем-то другим:
- Node.js и Bun - встроенный
fetch(), читающий ответ сContent-Encoding: br, вызовы Brotli изnode:zlibна недоверенных данных и парсеры тела запроса, которые распаковывают Brotli-загрузки (Express 5, body-parser 2). Обычный HTTP-сервер Node.js сам тела запросов не декодирует. - Python - достаточно установленного пакета Brotli или brotlicffi: тогда requests, urllib3, httpx и aiohttp сами декодируют Brotli-ответы, даже если ваш код никогда не импортирует Brotli.
- Envoy и Istio - только если настроен фильтр декомпрессии Brotli.
Любой сервис, который обращается к сторонним API, вебхукам, платежным провайдерам или парсит страницы, выполняет fetch() к серверам, которые вы не контролируете. Убрать br из Accept-Encoding - не исправление: сервер все равно может прислать Brotli.
| ПО | Уязвимые версии | Исправлено в |
|---|---|---|
| Node.js | 22.x, 24.x, 26.x до исправления; 20.x и старее (поддержка прекращена) | 22.23.4, 24.22.1, 26.12.1 |
| Bun | 1.4.3 и ранее | 1.4.4 |
| Python Brotli | 1.2.0 и ранее | 1.2.1 |
| Python brotlicffi | 1.2.0.x и ранее | 1.2.1.0 |
| Google Brotli (библиотека на C) | 1.0.3 - 1.2.0 | 1.0.10, 1.1.1, 1.2.1 |
| Envoy | 1.39.2, 1.38.5, 1.37.7, 1.36.11 | 1.40.0, 1.39.3, 1.38.6, 1.37.8, 1.36.12 |
| Istio | 1.31.0 - 1.31.1, 1.30.0 - 1.30.5, 1.29.0 - 1.29.8 | 1.31.2, 1.30.6, 1.29.9 |
Проверка сервера одной командой
Этот POSIX shell-скрипт проверяет Bun, Node.js и Python на машине (или внутри контейнера) и показывает, какие из них старше исправления. Сохраните его как brotli-check.sh:
#!/bin/sh
# CVE-2026-111497 (Brotli decoder): which runtimes on this host are older than the fixes?
ver_lt() { [ "$1" = "$2" ] && return 1; [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]; }
if command -v bun >/dev/null 2>&1; then
v=$(bun --version)
if ver_lt "$v" 1.4.4; then echo "bun $v: VULNERABLE, upgrade to 1.4.4+"; else echo "bun $v: fixed"; fi
fi
if command -v node >/dev/null 2>&1; then
v=$(node -p "process.versions.bun ? '' : process.versions.node")
if [ -z "$v" ]; then echo "node: this is Bun's node shim, see the bun line"; else major=${v%%.*}
case $major in
22) fix=22.23.4 ;; 24) fix=24.22.1 ;; 26) fix=26.12.1 ;;
*) fix= ;;
esac
if [ -z "$fix" ]; then echo "node $v: no fixed release for this line, move to 22, 24 or 26"
elif ver_lt "$v" "$fix"; then echo "node $v: VULNERABLE, upgrade to $fix+"
else echo "node $v: fixed"; fi
fi
fi
for py in python3 python; do
command -v $py >/dev/null 2>&1 || continue
$py - <<'PY'
from importlib import metadata
for pkg, fix in (("Brotli", "1.2.1"), ("brotlicffi", "1.2.1.0")):
try:
v = metadata.version(pkg)
except metadata.PackageNotFoundError:
print(f"python {pkg}: not installed")
continue
a = [int(x) for x in v.split(".") if x.isdigit()]
b = [int(x) for x in fix.split(".")]
print(f"python {pkg} {v}: {'VULNERABLE, upgrade to ' + fix + '+' if a < b else 'fixed'}")
PY
break
done
sh brotli-check.sh
docker exec my-app sh -s < brotli-check.sh # the same check inside a running container
Вывод проверен 2026-10-24 (ноутбук разработчика, затем официальные Docker-образы):
bun 1.4.1: VULNERABLE, upgrade to 1.4.4+
node 26.6.0: VULNERABLE, upgrade to 26.12.1+
python Brotli: not installed
python brotlicffi: not installed
bun 1.4.4: fixed
node: this is Bun's node shim, see the bun line
node 22.23.4: fixed
node 16.20.2: no fixed release for this line, move to 22, 24 or 26
python Brotli 1.1.0: VULNERABLE, upgrade to 1.2.1+
python brotlicffi: not installed
Запускайте скрипт в каждом контейнере, а не только на хосте: Node.js на хосте ничего не говорит о том, что зашит в образ.
Как раскатить исправление
- Найдите каждую копию. Чаще всего пропускают образы. Перечислите, что у вас запущено:
docker ps --format '{{.Image}}', затем строкиFROMв ваших Dockerfile. Тег вродеnode:22илиoven/bun:alpineобновляется только при pull; закрепленный тег, напримерoven/bun:1.4.1-slim, не обновляется никогда. - Обновите рантайм. Поднимите закрепленные теги (
oven/bun:1.4.4-slim,node:22.23.4-alpine), выполнитеbun upgradeи обновите пакеты Node.js на хостах, а в Python-окружениях -pip install -U "Brotli>=1.2.1"(илиbrotlicffi>=1.2.1.0). Если Python-приложению Brotli не нужен,pip uninstall Brotli brotlicffiтоже закрывает этот путь: HTTP-клиенты переходят на gzip и deflate. - Пересоберите и замените.
docker compose build --pull && docker compose up -d. Запущенный процесс держит старый декодер до перезапуска, так что перезапустите сервисы и на хостах. - Проверьте. Снова запустите скрипт проверки в каждом контейнере (
docker exec <name> bun --version- быстрый вариант). Lockfile с--frozen-lockfileмогут сломать пересборку после обновления зависимостей: коммитьте обновленный lockfile вместе сpackage.json. - Пока не можете обновиться? Отклоняйте запросы с
Content-Encoding: brна прокси, прежде чем что-либо декодирует тело, и не загружайте данные с недоверенных хостов. Vercel отмечает, что правила файрвола не покрывают ответы, которые ваш собственный код получает с других серверов, - помогает только обновление.
Для стека Node.js на VM см. Облачный сервер для Node.js, для Python - Облачный сервер для Python и Flask, а для образов, которые чисто пересобираются, - Облачный сервер для Docker Swarm.
Что сделали мы
За несколько дней после выхода исправлений мы перевели каждый сервис DCXV, работающий на Bun, на 1.4.4: закрепленные базовые образы перешли на исправленные теги, а все образы пересобраны со свежим pull. Наши компоненты на Node.js не декодируют тела Brotli, а у наших прокси нет декомпрессии Brotli. Серверами клиентов управляют их владельцы: если вы запускаете Node.js, Bun или Python на облачном сервере DCXV, выполните проверку выше сегодня или спросите нас об управляемом администрировании серверов.
Источники (проверено 2026-10-24): октябрьские релизы безопасности Node.js 2026 года (22.23.4, 24.22.1, 26.12.1), релиз Bun 1.4.4, оценка CISA от 23 октября 2026 года и исследование Imperva.
