Handlebars AST Injection Remote Code Execution — CVE-2026-33937
by dinhvaren · 2026-07-05
- Severity
- Critical
- CVE
- CVE-2026-33937
- Category
- web
- Affected product
- Handlebars (Node.js templating engine)
- Affected versions
- <= 4.7.8
- Disclosed
- 2026-07-05
- Patch status
- patched
Tags
References
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-05 |
| Last Updated | 2026-05 |
| Author / Researcher | dinhvaren |
| CVE / Advisory | CVE-2026-33937 |
| Category | web |
| Severity | Critical |
| CVSS Score | Not specified in source |
| Status | PoC |
| Tags | handlebars, rce, template-injection, ast-injection, nodejs, express, javascript-compiler, code-generation |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | Handlebars (Node.js templating engine) |
| Versions Affected | <= 4.7.8 |
| Language / Platform | Node.js / Express, demonstrated via an example e-commerce-style web app |
| Authentication Required | No |
| Network Access Required | Yes |
Summary
Handlebars’ Handlebars.compile() accepts either a plain template string or a pre-parsed AST object; when given an AST object directly, the normal template-parsing phase (which would otherwise escape/validate literal values) is skipped entirely. Inside the JavaScript compiler, the NumberLiteral node handler inserts the node’s value field directly into the generated JavaScript source without any sanitization. By crafting a NumberLiteral node whose value is a string containing malicious JavaScript instead of a number, an attacker can break out of the generated code and inject arbitrary JavaScript that executes when the compiled template runs. The included demo app is a small Express/Handlebars e-commerce-style server (product listing, cart, auth, admin, profile rendering) with a dedicated /render endpoint that accepts an inputType/payload/context body and passes the (optionally JSON-parsed) payload straight into Handlebars.compile(), giving an unauthenticated remote caller a direct path to submit a malicious AST and achieve remote code execution.
Vulnerability Details
Root Cause
lib/handlebars/compiler/javascript-compiler.js’s NumberLiteral handler (this.pushStackLiteral(number.value)) inserts the AST node’s value field directly into generated JavaScript source code without validating that it is actually numeric or escaping it, and Handlebars.compile() allows a caller to supply a pre-parsed AST object directly, bypassing the string-template parser that would normally have produced only safe literal values.
Attack Vector
- Attacker sends a request to an application endpoint that passes user-controlled input into
Handlebars.compile()— in this PoC,POST /renderwith a JSON body containingpayload. - Instead of a template string, the attacker supplies a full pre-parsed Handlebars AST (JSON) whose
bodycontains aMustacheStatementcallinglookupwith aNumberLiteralparameter whosevaluefield is a string like"{},{})) + process.getBuiltinModule('child_process').execFileSync('id').toString() //". Handlebars.compile()skips parsing (since input is already an AST) and theJavaScriptCompiler’sNumberLiteralhandler embeds that string verbatim into the generated function body.- The malformed literal breaks out of its intended expression context, and the resulting
new Function(...)/eval-generated code executes the attacker’s injectedchild_processcall when the compiled template is invoked — achieving RCE in the Node.js process.
Impact
Full remote code execution in the Node.js server process hosting the vulnerable Handlebars template compilation, leading to complete server compromise (arbitrary command execution, data access, lateral movement).
Environment / Lab Setup
Target: Node.js application using Handlebars <= 4.7.8 with any code path passing untrusted input into Handlebars.compile() (demo: Express app with /render endpoint)
Attacker: curl / HTTP client capable of sending a JSON POST body containing the crafted AST payload
Proof of Concept
PoC Script
See
server.js,src/routes/render.route.js,src/services/render.service.js, and the rest of the demo app in this folder.
| |
The demo server exposes a /render route (src/routes/render.route.js) that forwards the request body’s payload/context into renderUserSuppliedInput() (src/services/render.service.js), which JSON-parses the payload if it looks like an object/array and calls Handlebars.compile() on it directly — so submitting the crafted AST JSON as payload triggers the NumberLiteral code-injection path and executes the attacker’s embedded child_process call on the server.
Detection & Indicators of Compromise
Signs of compromise:
- Unexpected child process spawns (e.g.
id, shell commands) originating from the Node.js process hosting the Handlebars-based application, with no corresponding legitimate feature that spawns processes. - Requests to template-rendering endpoints whose body contains raw Handlebars AST JSON structures (
"type":"Program","NumberLiteral","MustacheStatement") instead of plain template strings. - Anomalous outbound network activity or file access immediately following a render/preview endpoint call.
Remediation
| Action | Detail |
|---|---|
| Primary fix | Upgrade to a patched Handlebars release that validates/rejects non-numeric NumberLiteral.value input during code generation; no vendor patch version confirmed as of 2026-07-05 — monitor for an official Handlebars advisory. |
| Interim mitigation | Never pass user-controlled or externally-supplied data into Handlebars.compile() as a pre-parsed AST object — only accept plain template strings from trusted sources, and validate/sanitize any dynamic template input before compilation. |
References
Notes
Mirrored from https://github.com/dinhvaren/cve-2026-33937 on 2026-07-05.
| |