本文是由 EQSTLab 发布的研究文章。
参考 PoC:dinhvaren/cve-2026-33937
★ CVE-2026-33937 Handlebars.js AST 注入远程代码执行 PoC ★
https://github.com/user-attachments/assets/99e383e7-e71f-4e10-8c62-f50abff8b4f5
CVE-2026-33937:Handlebars.js AST 注入远程代码执行漏洞
| 受影响版本 | 4.0.0 – 4.7.8 |
| 已修补版本 | 4.7.9 |
| CVSS 评分 | 9.8(严重) |
Handlebars.js 中存在一个类型混淆漏洞,起因是 Handlebars.compile() 不仅接受模板字符串,还接受预先解析的 AST 对象(JSON)作为输入。攻击者如果能控制传递给 compile() 的值,就可以提供一个精心构造的 AST 对象,将 NumberLiteral 节点的 value 字段替换为任意 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 # build + run in one step
应用程序运行在 http://localhost:12701。
Web 界面
在浏览器中打开 http://localhost:12701。页面提供一个 B2B 邮件构建器界面。用户提供的字段值会在客户端被插值到 Handlebars 模板字符串中,然后提交到服务器进行渲染。可通过输入字段或直接通过 API 注入恶意 Handlebars payload。
直接调用 API
# POST crafted template to the vulnerable endpoint
curl -s -X POST http://localhost:12701/api/email/preview \
-H "Content-Type: application/json" \
-d '{"subject":"test","editorTemplateData":"<PAYLOAD>"}'
将 <PAYLOAD> 替换为针对 Handlebars.compile() 的 AST 注入 payload。
POST /api/email/preview
服务器接受包含 editorTemplateData 的 JSON 请求体,并将其直接传递给 Handlebars.compile(),没有任何净化或白名单校验:
// app.js
const renderEmail = Handlebars.compile(editorTemplateData);
Handlebars.compile() 的文档说明其接受模板字符串,但其内部类型检查也允许直接传入预先解析的 AST 对象。在 javascript-compiler.js 中,NumberLiteral() 访问器会将节点的 value 字段逐字输出到生成的 JavaScript 源码中,而不进行任何类型验证或净化:
// javascript-compiler.js (simplified)
NumberLiteral(number) {
this.pushStackLiteral(number.value); // value inserted as-is into emitted JS
}
如果攻击者提供精心构造的 AST 对象,使 number.value 是一个包含任意 JavaScript 的字符串(例如 "1; require('child_process').execSync(...)")而不是数字字面量,那么生成的函数体就会包含并在渲染时执行该代码。
存在漏洞的应用程序中的危险模式是:
const render = Handlebars.compile(userInput); // userInput may be a crafted AST object
render(safeContextData); // attacker code runs here
关于利用技术的说明: 现有的公开 PoC(包括所引用的那个)通过
NumberLiteral节点结合lookup辅助函数实现注入。本 PoC 则使用BooleanLiteral节点结合内置的log辅助函数确认了相同的 RCE 原语,证明该类型混淆并不仅限于某一种节点类型或辅助函数。
/api/email/preview 提交一个包含精心构造的 editorTemplateData 值的 POST 请求。Handlebars.compile()。render() 会在服务器进程中触发任意 JavaScript 的执行。当 Handlebars 被部署在 Node.js 后端环境时,此漏洞会导致服务器端代码执行路径,这与标准的客户端 XSS 不同。该攻击不要求任何输出被回显给用户;payload 会在 Node.js 进程内以与应用程序相同的权限执行。根据部署情况的不同:
/etc/passwd)。+-------------------------------------------+
| Attacker |
+-------------------------------------------+
|
| POST /api/email/preview
| {"editorTemplateData": "<malicious payload>"}
v
+-------------------------------------------+
| Handlebars.compile(editorTemplateData) |
| (No sanitization — app.js:17) |
+-------------------------------------------+
|
| AST node injection
| breaks template sandbox
v
+-------------------------------------------+
| Arbitrary JS Execution (Server Process) |
+-------------------------------------------+
|
| Read sensitive files, spawn shell,
| exfiltrate secrets, etc.
v
+-------------------------------------------+
| Remote Code Execution (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 进行构建时预编译。
在构建时使用 Handlebars CLI 预编译模板,并且只发布运行时包。运行时构建不包含 compile(),从而彻底消除生产部署中的攻击面。
本仓库仅供安全研究、教育和受控的漏洞演示使用。未经系统所有者的明确书面授权,不得用于测试或利用任何系统。本项目的目的是帮助安全研究人员、防御者和开发者理解该漏洞、在受控实验环境中验证暴露面,并应用有效的缓解措施。