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.
