
Уязвимость удалённого выполнения кода посредством инъекции AST в Handlebars.js
Эта публикация является исследовательской статьей, опубликованной EQSTLab.
Упомянутый PoC: dinhvaren/cve-2026-33937
★ CVE-2026-33937 PoC удаленного выполнения кода через AST-инъекцию в Handlebars.js ★
https://github.com/user-attachments/assets/99e383e7-e71f-4e10-8c62-f50abff8b4f5
CVE-2026-33937 : Уязвимость удаленного выполнения кода через AST-инъекцию в Handlebars.js
| Затронутые версии | 4.0.0 – 4.7.8 |
| Исправленная версия | 4.7.9 |
| Оценка CVSS | 9.8 (критическая) |
Уязвимость, связанная с путаницей типов (Type Confusion) в Handlebars.js, возникает из-за того, что Handlebars.compile() принимает на вход не только строку шаблона, но и предварительно разобранный объект AST (JSON). Атакующий, контролирующий значение, передаваемое в compile(), может подставить специально сформированный объект AST, в котором поле value узла NumberLiteral заменено произвольной JavaScript-строкой. Поскольку внутренний генератор кода вставляет это поле непосредственно в тело генерируемой JavaScript-функции без санитизации, результирующая функция выполняет управляемый атакующим код при рендеринге — что приводит к удаленному выполнению кода на сервере.
Соберите и запустите уязвимое окружение с помощью Docker:
Сборка образа
docker build -t cve-2026-33937-server .
Запуск контейнера
docker run --name cve-2026-33937 -p 12701:12701 -p 9229:9229 cve-2026-33937-server
Или используйте предоставленные npm-скрипты:
npm run docker:up # сборка + запуск одним шагом
Приложение доступно по адресу http://localhost:12701.
Веб-интерфейс
Откройте http://localhost:12701 в браузере. Страница представляет собой интерфейс B2B-конструктора писем. Введенные пользователем значения полей интерполируются в строку шаблона Handlebars на стороне клиента, а затем отправляются на сервер для рендеринга. Внедрите вредоносную Handlebars-нагрузку через поля ввода или напрямую через API.
Прямой вызов API
# POST с подделанным шаблоном на уязвимую конечную точку
curl -s -X POST http://localhost:12701/api/email/preview \
-H "Content-Type: application/json" \
-d '{"subject":"test","editorTemplateData":"<PAYLOAD>"}'
Замените <PAYLOAD> на нагрузку AST-инъекции, нацеленную на Handlebars.compile().
POST /api/email/preview
Сервер принимает JSON-тело, содержащее editorTemplateData, и передает его напрямую в Handlebars.compile() без санитизации и проверки по белому списку:
// app.js
const renderEmail = Handlebars.compile(editorTemplateData);
Документация Handlebars.compile() описывает его как принимающий строку шаблона, однако внутренняя проверка типов также позволяет передавать напрямую предварительно разобранный объект AST. Внутри javascript-compiler.js посетитель NumberLiteral() вставляет поле value узла в генерируемый JavaScript-код дословно, без какой-либо проверки типа или санитизации:
// javascript-compiler.js (упрощенно)
NumberLiteral(number) {
this.pushStackLiteral(number.value); // value вставляется как есть в генерируемый JS
}
Если атакующий предоставляет подделанный объект AST, в котором number.value является строкой, содержащей произвольный JavaScript (например, "1; require('child_process').execSync(...)"), вместо числового литерала, тело сгенерированной функции содержит и выполняет этот код во время рендеринга.
Опасный паттерн в уязвимом приложении:
const render = Handlebars.compile(userInput); // userInput может быть подделанным объектом AST
render(safeContextData); // здесь выполняется код атакующего
Примечание о технике эксплуатации: Существующие публичные PoC (включая упомянутый) достигают инъекции через узел
NumberLiteralв сочетании с хелперомlookup. Данный PoC подтверждает тот же примитив RCE с использованием узлаBooleanLiteralв сочетании со встроенным хелперомlog, демонстрируя, что путаница типов не ограничивается одним типом узла или хелпер-функцией.
/api/email/preview с подделанным значением editorTemplateData.Handlebars.compile() для контролируемого атакующим объекта AST.render() запускает выполнение произвольного JavaScript в процессе сервера.Когда Handlebars используется в бэкенд-среде Node.js, эта уязвимость приводит к пути выполнения кода на серверной стороне, в отличие от стандартного клиентского XSS. Атака не требует отражения какого-либо вывода пользователю; нагрузка выполняется внутри процесса Node.js с теми же привилегиями, что и приложение. В зависимости от развертывания:
/etc/passwd) могут быть прочитаны и выведены наружу.+-------------------------------------------+
| Атакующий |
+-------------------------------------------+
|
| POST /api/email/preview
| {"editorTemplateData": "<вредоносная нагрузка>"}
v
+-------------------------------------------+
| Handlebars.compile(editorTemplateData) |
| (Без санитизации — app.js:17) |
+-------------------------------------------+
|
| Инъекция AST-узла
| нарушает песочницу шаблона
v
+-------------------------------------------+
| Выполнение произвольного JS (процесс сервера) |
+-------------------------------------------+
|
| Чтение чувствительных файлов, запуск shell,
| вывод секретов и т.д.
v
+-------------------------------------------+
| Удаленное выполнение кода (RCE) |
+-------------------------------------------+
Обновите Handlebars.js до версии 4.7.9 или новее.
Версия 4.7.9 вводит строгую проверку типа входных данных в compile(), отклоняя аргументы, не являющиеся строками, до начала генерации кода.
Выполняйте проверку типа перед вызовом compile().
Если немедленное обновление невозможно, убедитесь, что аргумент всегда является string, в месте вызова:
if (typeof templateInput !== 'string') throw new TypeError('Template must be a string');
const render = Handlebars.compile(templateInput);
Используйте handlebars/runtime для предварительной компиляции на этапе сборки.
Предварительно компилируйте шаблоны на этапе сборки с помощью CLI Handlebars и поставляйте только runtime-сборку. Runtime-сборка не включает compile(), что полностью устраняет поверхность атаки для производственных развертываний.
Этот репозиторий предназначен исключительно для исследований в области безопасности, обучения и контролируемой демонстрации уязвимостей. Его нельзя использовать для тестирования или эксплуатации систем без явного письменного разрешения владельца системы. Цель этого проекта — помочь исследователям безопасности, специалистам по защите и разработчикам понять уязвимость, проверить подверженность в контролируемых лабораторных средах и применить эффективные меры по смягчению.