
Handlebars.js AST-Injection-Schwachstelle für Remote-Codeausführung
Dieser Beitrag ist ein Forschungsartikel, der von EQSTLab veröffentlicht wurde.
Referenziertes PoC: dinhvaren/cve-2026-33937
★ CVE-2026-33937 Handlebars.js AST-Injection Remote-Code-Execution-PoC ★
https://github.com/user-attachments/assets/99e383e7-e71f-4e10-8c62-f50abff8b4f5
CVE-2026-33937 : Schwachstelle zur Remote-Code-Ausführung durch AST-Injection in Handlebars.js
| Betroffene Versionen | 4.0.0 – 4.7.8 |
| Behobene Version | 4.7.9 |
| CVSS-Score | 9.8 (Kritisch) |
Eine Type-Confusion-Schwachstelle in Handlebars.js entsteht dadurch, dass Handlebars.compile() nicht nur eine Template-Zeichenfolge, sondern auch ein vorgeparstes AST-Objekt (JSON) als Eingabe akzeptiert. Ein Angreifer, der den an compile() übergebenen Wert kontrolliert, kann ein manipuliertes AST-Objekt bereitstellen, bei dem das value-Feld eines NumberLiteral-Knotens durch einen beliebigen JavaScript-String ersetzt wird. Da der interne Codegenerator dieses Feld ohne Bereinigung direkt in den erzeugten JavaScript-Funktionsrumpf einfügt, führt die resultierende Funktion bei der Darstellung (Render) vom Angreifer kontrollierten Code aus – was eine Remote Code Execution auf dem Server ermöglicht.
Erstellen und Ausführen der verwundbaren Umgebung mit Docker:
Image erstellen
docker build -t cve-2026-33937-server .
Container ausführen
docker run --name cve-2026-33937 -p 12701:12701 -p 9229:9229 cve-2026-33937-server
Oder verwenden Sie die bereitgestellten npm-Skripte:
npm run docker:up # build + run in one step
Die Anwendung ist unter http://localhost:12701 erreichbar.
Web-UI
Öffnen Sie http://localhost:12701 in einem Browser. Die Seite stellt eine B2B-E-Mail-Builder-Oberfläche bereit. Vom Benutzer gelieferte Feldwerte werden clientseitig in einen Handlebars-Template-String interpoliert und anschließend an den Server zum Rendern gesendet. Injizieren Sie eine schädliche Handlebars-Nutzlast über die Eingabefelder oder direkt über die API.
Direkte 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>"}'
Ersetzen Sie <PAYLOAD> durch eine AST-Injection-Nutzlast, die auf Handlebars.compile() abzielt.
POST /api/email/preview
Der Server akzeptiert einen JSON-Body, der editorTemplateData enthält, und übergibt ihn ohne Bereinigung oder Allowlist-Prüfung direkt an Handlebars.compile():
// app.js
const renderEmail = Handlebars.compile(editorTemplateData);
Handlebars.compile() ist dokumentiert als Funktion, die eine Template-Zeichenfolge akzeptiert, aber ihre interne Typprüfung erlaubt auch, ein vorgeparstes AST-Objekt direkt zu übergeben. Innerhalb von javascript-compiler.js gibt der NumberLiteral()-Visitor das value-Feld des Knotens wörtlich in den erzeugten JavaScript-Quellcode aus, ohne Typvalidierung oder Bereinigung:
// javascript-compiler.js (simplified)
NumberLiteral(number) {
this.pushStackLiteral(number.value); // value inserted as-is into emitted JS
}
Wenn ein Angreifer ein manipuliertes AST-Objekt bereitstellt, bei dem number.value anstelle eines numerischen Literals ein String mit beliebigem JavaScript ist (z. B. "1; require('child_process').execSync(...)"), enthält der erzeugte Funktionsrumpf diesen Code und führt ihn beim Rendern aus.
Das gefährliche Muster in der verwundbaren Anwendung ist:
const render = Handlebars.compile(userInput); // userInput may be a crafted AST object
render(safeContextData); // attacker code runs here
Hinweis zur Ausnutzungstechnik: Öffentlich verfügbare PoCs (einschließlich des referenzierten) erreichen die Injektion über einen
NumberLiteral-Knoten in Kombination mit einemlookup-Helper. Dieses PoC bestätigt dieselbe RCE-Primitive unter Verwendung einesBooleanLiteral-Knotens in Kombination mit dem eingebautenlog-Helper und zeigt, dass die Type Confusion nicht auf einen einzelnen Knotentyp oder eine einzelne Helferfunktion beschränkt ist.
/api/email/preview mit einem manipulierten editorTemplateData-Wert.Handlebars.compile() mit dem vom Angreifer kontrollierten AST-Objekt auf.render() löst die Ausführung beliebigen JavaScripts im Serverprozess aus.Wenn Handlebars in einer Node.js-Backend-Umgebung eingesetzt wird, führt diese Schwachstelle zu einem serverseitigen Codeausführungspfad – anders als bei standardmäßigem clientseitigem XSS. Der Angriff erfordert keine Ausgabe, die an einen Benutzer zurückgespiegelt wird; die Nutzlast wird innerhalb des Node.js-Prozesses mit denselben Rechten wie die Anwendung ausgeführt. Abhängig von der Bereitstellung:
/etc/passwd) können gelesen und exfiltriert werden.+-------------------------------------------+
| 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) |
+-------------------------------------------+
Aktualisieren Sie Handlebars.js auf 4.7.9 oder neuer.
Version 4.7.9 führt eine strikte Eingabetypprüfung in compile() ein, die Nicht-String-Argumente ablehnt, bevor die Codegenerierung beginnt.
Erzwingen Sie eine Typprüfung vor dem Aufruf von compile().
Wenn ein sofortiges Upgrade nicht möglich ist, validieren Sie an der Aufrufstelle, dass das Argument immer ein string ist:
if (typeof templateInput !== 'string') throw new TypeError('Template must be a string');
const render = Handlebars.compile(templateInput);
Verwenden Sie handlebars/runtime für die Vorkompilierung zur Buildzeit.
Kompilieren Sie Vorlagen zur Buildzeit mit der Handlebars-CLI vor und liefern Sie nur das Runtime-Bundle aus. Die Runtime-Build enthält kein compile() und eliminiert damit die Angriffsfläche vollständig für Produktionsbereitstellungen.
Dieses Repository dient ausschließlich der Sicherheitsforschung, Ausbildung und kontrollierten Demonstration von Schwachstellen. Es darf nicht verwendet werden, um Systeme ohne ausdrückliche schriftliche Genehmigung des Systeminhabers zu testen oder auszunutzen. Der Zweck dieses Projekts ist es, Sicherheitsforschern, Verteidigern und Entwicklern zu helfen, die Schwachstelle zu verstehen, die Gefährdung in kontrollierten Laborumgebungen zu validieren und wirksame Gegenmaßnahmen anzuwenden.