
Vulnérabilité d'exécution de code à distance par injection AST dans Handlebars.js
Cet article est une publication de recherche de l'EQSTLab.
PoC référencé : dinhvaren/cve-2026-33937
★ CVE-2026-33937 — PoC d'injection AST et d'exécution de code à distance dans Handlebars.js ★
https://github.com/user-attachments/assets/99e383e7-e71f-4e10-8c62-f50abff8b4f5
CVE-2026-33937 : vulnérabilité d'exécution de code à distance par injection AST dans Handlebars.js
| Versions affectées | 4.0.0 – 4.7.8 |
| Version corrigée | 4.7.9 |
| Score CVSS | 9.8 (Critique) |
Une vulnérabilité de confusion de type dans Handlebars.js provient du fait que Handlebars.compile() accepte non seulement une chaîne de modèle, mais aussi un objet AST pré-analysé (JSON) en entrée. Un attaquant qui contrôle la valeur passée à compile() peut fournir un objet AST malveillant dans lequel le champ value d'un nœud NumberLiteral est remplacé par une chaîne JavaScript arbitraire. Étant donné que le générateur de code interne insère ce champ directement dans le corps de la fonction JavaScript émise sans assainissement, la fonction résultante exécute le code contrôlé par l'attaquant lors du rendu — permettant une exécution de code à distance sur le serveur.
Créez et exécutez l'environnement vulnérable avec Docker :
Construire l'image
docker build -t cve-2026-33937-server .
Exécuter le conteneur
docker run --name cve-2026-33937 -p 12701:12701 -p 9229:9229 cve-2026-33937-server
Ou utilisez les scripts npm fournis :
npm run docker:up # build + run in one step
L'application est servie à l'adresse http://localhost:12701.
Interface web
Ouvrez http://localhost:12701 dans un navigateur. La page présente une interface de création d'e-mails B2B. Les valeurs de champs fournies par l'utilisateur sont interpolées dans une chaîne de modèle Handlebars côté client, puis envoyées au serveur pour le rendu. Injectez une charge utile Handlebars malveillante via les champs de saisie ou directement via l'API.
API directe
# 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>"}'
Remplacez <PAYLOAD> par une charge utile d'injection AST ciblant Handlebars.compile().
POST /api/email/preview
Le serveur accepte un corps JSON contenant editorTemplateData et le transmet directement à Handlebars.compile() sans assainissement ni application de liste autorisée :
// app.js
const renderEmail = Handlebars.compile(editorTemplateData);
Handlebars.compile() est documenté comme acceptant une chaîne de modèle, mais sa vérification de type interne permet également de passer directement un objet AST pré-analysé. Dans javascript-compiler.js, le visiteur NumberLiteral() émet le champ value du nœud tel quel dans la source JavaScript générée, sans validation de type ni assainissement :
// javascript-compiler.js (simplified)
NumberLiteral(number) {
this.pushStackLiteral(number.value); // value inserted as-is into emitted JS
}
Si un attaquant fournit un objet AST malveillant dans lequel number.value est une chaîne contenant du JavaScript arbitraire (par exemple, "1; require('child_process').execSync(...)") au lieu d'un littéral numérique, le corps de la fonction émise contient et exécute ce code lors du rendu.
Le schéma dangereux dans l'application vulnérable est :
const render = Handlebars.compile(userInput); // userInput may be a crafted AST object
render(safeContextData); // attacker code runs here
Note sur la technique d'exploitation : Les PoC publics existants (y compris celui référencé) réalisent l'injection via un nœud
NumberLiteralcombiné à un helperlookup. Ce PoC confirme la même primitive RCE en utilisant un nœudBooleanLiteralcombiné au helper intégrélog, démontrant que la confusion de type n'est pas limitée à un seul type de nœud ou à une seule fonction helper.
/api/email/preview avec une valeur editorTemplateData malveillante.Handlebars.compile() sur l'objet AST contrôlé par l'attaquant.render() déclenche l'exécution de JavaScript arbitraire dans le processus serveur.Lorsque Handlebars est utilisé dans un environnement backend Node.js, cette vulnérabilité mène à une exécution de code côté serveur, contrairement à une XSS classique côté client. L'attaque n'exige pas qu'une sortie soit renvoyée à l'utilisateur ; la charge utile s'exécute dans le processus Node.js avec les mêmes privilèges que l'application. Selon le déploiement :
/etc/passwd) peuvent être lus et exfiltrés.+-------------------------------------------+
| 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) |
+-------------------------------------------+
Mettez à niveau Handlebars.js vers 4.7.9 ou une version ultérieure.
La version 4.7.9 introduit une validation stricte du type d'entrée dans compile(), rejetant les arguments qui ne sont pas des chaînes avant le début de la génération de code.
Appliquez une vérification de type avant d'appeler compile().
Si une mise à niveau immédiate n'est pas possible, validez que l'argument est toujours une string au niveau du site d'appel :
if (typeof templateInput !== 'string') throw new TypeError('Template must be a string');
const render = Handlebars.compile(templateInput);
Utilisez handlebars/runtime pour la précompilation au moment du build.
Précompilez les modèles au moment du build avec la CLI Handlebars et distribuez uniquement le bundle runtime. La version runtime n'inclut pas compile(), ce qui élimine entièrement la surface d'attaque pour les déploiements en production.
Ce dépôt est destiné uniquement à la recherche en sécurité, à l'éducation et à la démonstration contrôlée de vulnérabilités. Il ne doit pas être utilisé pour tester ou exploiter des systèmes sans l'autorisation écrite explicite du propriétaire du système. L'objectif de ce projet est d'aider les chercheurs en sécurité, les défenseurs et les développeurs à comprendre la vulnérabilité, à valider l'exposition dans des environnements de laboratoire contrôlés et à appliquer des mesures d'atténuation efficaces.