В Handlebars.js нашли обход прошлого патча: CVE-2026-106446 даёт выполнить код на Node.js-сервере

5 октября 2026 года мейнтейнеры Handlebars.js опубликовали критическую уязвимость CVE-2026-106446 (CVSS 9.8): если приложение передаёт в Handlebars.compile() или precompile() объект вместо строки шаблона, атакующий выполняет произвольный JavaScript в процессе Node.js. Уязвимы версии 4.0.0–4.7.9, исправление — 4.7.10.
Это не первая дыра такого типа: новая уязвимость обходит проверку AST, которую добавили в 4.7.9 после CVE-2026-33937. PoC опубликован прямо в бюллетене, а CISA в своей оценке SSVC отметила, что эксплуатация автоматизируется и даёт полный контроль над процессом. Handlebars сидит в зависимостях огромного числа веб-приложений, сборщиков и генераторов документации, причём чаще транзитивно, чем напрямую.
Ниже — как устроена атака, кого она действительно затрагивает, как найти Handlebars в дереве зависимостей и что делать, если обновиться сразу нельзя. Если в проекте давно не проверяли сторонние пакеты, начните с нашего разбора аудита кода и того, что он проверяет.
Что раскрыли 5 октября
Бюллетень GHSA-8r5x-fm3f-whwj называется «JavaScript Injection via AST Type Confusion in compile». 6 октября запись CVE-2026-106446 появилась в NVD с оценкой CVSS 3.1 9.8 (вектор AV:N/AC:L/PR:N/UI:N) и классами слабостей CWE-94 (внедрение кода) и CWE-843 (путаница типов). Ключевые факты:
- затронуты все версии Handlebars от 4.0.0 до 4.7.9 включительно;
- исправление вышло в версии 4.7.10, вместе с ним закрыты ещё несколько проблем шаблонизатора;
- уязвимость — обход защиты, добавленной в 4.7.9 для CVE-2026-33937 (CVSS 9.8, тот же класс атаки через NumberLiteral);
- в бюллетене есть рабочий пример эксплуатации; по данным GitHub, найдено три варианта атаки на разных типах узлов AST.
Как работает атака через подменённый AST
Handlebars умеет принимать не только текст шаблона, но и уже разобранное синтаксическое дерево (AST). Компилятор превращает это дерево в JavaScript-код. В 4.7.9 разработчики начали проверять часть узлов — PathExpression, NumberLiteral и BooleanLiteral, — но другие значения по-прежнему попадали в сгенерированный код как есть.
Атакующий подкладывает объект, в котором вместо числа или строки стоит JavaScript-выражение: например, в поле Program.blockParams.length, в глубину параметра, в нестроковое StringLiteral.value или PathExpression.original. При вызове compile() код исполняется в процессе Node.js с правами приложения — это полноценное удалённое выполнение кода. При precompile() вредоносный фрагмент встраивается в готовый шаблон и срабатывает везде, где этот шаблон потом загрузят, в том числе в браузере пользователя.
Кого уязвимость не касается
Важная оговорка, которую часто теряют в пересказах: приложения, которые передают в Handlebars только строки шаблонов, не уязвимы. Опасный сценарий — когда в compile() попадает объект из внешнего источника. GitHub называет самый частый путь: тело JSON-запроса, которое без проверки типа уходит прямо в компиляцию шаблона. Это встречается в конструкторах писем и документов, CMS с пользовательскими шаблонами и сервисах, где клиенты сами настраивают уведомления.
Поэтому сканер зависимостей будет подсвечивать CVE-2026-106446 почти в каждом JavaScript-проекте, но реальный риск нужно оценивать по коду: откуда берётся аргумент compile() и может ли туда попасть недоверенный объект.
Как найти Handlebars в своих проектах
Handlebars редко подключают напрямую — чаще он приходит вместе с инструментами сборки, генераторами отчётов и тестовыми фреймворками. Порядок проверки:
1. Выполните npm ls handlebars (или yarn why handlebars, pnpm why handlebars) в каждом репозитории и зафиксируйте все версии ниже 4.7.10.
2. Разделите находки на рантайм и dev-зависимости. Пакет, который работает только при сборке на CI, важен меньше, чем шаблонизатор в продакшен-сервисе, который принимает запросы.
3. Для рантайма найдите в коде все вызовы compile и precompile и проверьте, откуда берётся аргумент.
4. Если прямая зависимость ещё не обновилась, поднимите версию принудительно через overrides (npm), resolutions (Yarn) или pnpm.overrides и прогоните тесты.
Что делать, если обновиться сразу нельзя
Мейнтейнеры предлагают два временных решения. Первое — жёсткая проверка типа перед компиляцией: если входные данные не строка, выбрасывать ошибку TypeError. Второе — на серверах, где шаблоны заранее компилируются при сборке, подключать только runtime-сборку handlebars/runtime: в ней нет compile(), и атаковать нечего. Оба шага не заменяют обновление до 4.7.10, но закрывают основной вектор, пока команда готовит релиз.
После обновления стоит проверить и прекомпилированные шаблоны: если их собирали из недоверенных данных уязвимой версией, они могли сохранить внедрённый код. Такие артефакты надёжнее пересобрать.
Часто задаваемые вопросы
Какие версии Handlebars уязвимы к CVE-2026-106446? Все версии с 4.0.0 по 4.7.9 включительно. Исправление — в версии 4.7.10, вышедшей 5 октября 2026 года.
Нужно ли срочно обновляться, если мы передаём в compile() только строки? Прямого риска нет, но обновиться всё равно стоит: код меняется, и через год в compile() может попасть объект. К тому же 4.7.10 закрывает и другие уязвимости шаблонизатора.
Связана ли уязвимость с CVE-2026-33937? Да. Это обход защиты, которую добавили в 4.7.9 после CVE-2026-33937. Если вы обновились только до 4.7.9, проект остаётся уязвимым.
Есть ли публичный эксплойт? Да, пример эксплуатации опубликован в бюллетене GitHub, а CISA в оценке SSVC указывает статус PoC и автоматизируемость атаки.
Источники
GitHub Security Advisory GHSA-8r5x-fm3f-whwj — JavaScript Injection via AST Type Confusion in compile, 5 октября 2026 года (первоисточник, мейнтейнеры Handlebars).
NIST NVD — CVE-2026-106446, опубликовано 6 октября 2026 года, с оценкой SSVC от CISA.
Handlebars.js — релиз v4.7.10.
SecurityOnline — Handlebars.js RCE Flaws Disclosed With Public PoC.
Найдём уязвимые зависимости до того, как их найдут атакующие
Проверим дерево npm-пакетов и код ваших сервисов: где стоят уязвимые версии Handlebars и других библиотек, какие из них реально достижимы из внешних запросов и что обновить в первую очередь.