
Vulnerabilidad de ejecución remota de código por inyección en el AST de Handlebars.js
Esta publicación es un artículo de investigación publicado por EQSTLab.
PoC de referencia: dinhvaren/cve-2026-33937
★ CVE-2026-33937 PoC de inyección de AST y ejecución remota de código en Handlebars.js ★
https://github.com/user-attachments/assets/99e383e7-e71f-4e10-8c62-f50abff8b4f5
CVE-2026-33937 : Vulnerabilidad de inyección de AST y ejecución remota de código en Handlebars.js
| Versiones afectadas | 4.0.0 – 4.7.8 |
| Versión parcheada | 4.7.9 |
| Puntuación CVSS | 9.8 (Crítica) |
La vulnerabilidad de confusión de tipos (Type Confusion) en Handlebars.js surge del hecho de que Handlebars.compile() acepta no solo una cadena de plantilla, sino también un objeto AST preanalizado (JSON) como entrada. Un atacante que controle el valor pasado a compile() puede suministrar un objeto AST manipulado en el que el campo value de un nodo NumberLiteral se reemplaza con una cadena JavaScript arbitraria. Debido a que el generador de código interno inserta ese campo directamente en el cuerpo de la función JavaScript emitida sin saneamiento, la función resultante ejecuta código controlado por el atacante al renderizar, logrando ejecución remota de código en el servidor.
Compila y ejecuta el entorno vulnerable usando Docker:
Construir imagen
docker build -t cve-2026-33937-server .
Ejecutar el contenedor
docker run --name cve-2026-33937 -p 12701:12701 -p 9229:9229 cve-2026-33937-server
O usa los scripts npm proporcionados:
npm run docker:up # build + run in one step
La aplicación se sirve en http://localhost:12701.
Interfaz web
Abre http://localhost:12701 en un navegador. La página presenta una interfaz de creación de correos electrónicos B2B. Los valores de los campos proporcionados por el usuario se interpolan en una cadena de plantilla de Handlebars en el lado del cliente y luego se envían al servidor para su renderizado. Inyecta una carga útil maliciosa de Handlebars a través de los campos de entrada o directamente mediante la API.
API directa
# 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>"}'
Reemplaza <PAYLOAD> con una carga útil de inyección de AST dirigida a Handlebars.compile().
POST /api/email/preview
El servidor acepta un cuerpo JSON que contiene editorTemplateData y lo pasa directamente a Handlebars.compile() sin saneamiento ni aplicación de listas permitidas:
// app.js
const renderEmail = Handlebars.compile(editorTemplateData);
Handlebars.compile() está documentado para aceptar una cadena de plantilla, pero su verificación interna de tipos también permite pasar directamente un objeto AST preanalizado. Dentro de javascript-compiler.js, el visitante NumberLiteral() emite el campo value del nodo tal cual en el código JavaScript generado, sin validación de tipo ni saneamiento:
// javascript-compiler.js (simplified)
NumberLiteral(number) {
this.pushStackLiteral(number.value); // value inserted as-is into emitted JS
}
Si un atacante proporciona un objeto AST manipulado donde number.value es una cadena que contiene JavaScript arbitrario (por ejemplo, "1; require('child_process').execSync(...)") en lugar de un literal numérico, el cuerpo de la función emitida contiene y ejecuta ese código en el momento del renderizado.
El patrón peligroso en la aplicación vulnerable es:
const render = Handlebars.compile(userInput); // userInput may be a crafted AST object
render(safeContextData); // attacker code runs here
Nota sobre la técnica de explotación: Los PoC públicos existentes (incluido el de referencia) logran la inyección mediante un nodo
NumberLiteralcombinado con el helperlookup. Este PoC confirma la misma primitiva de RCE usando un nodoBooleanLiteralcombinado con el helper integradolog, demostrando que la confusión de tipos no se limita a un solo tipo de nodo o función auxiliar.
/api/email/preview con un valor editorTemplateData manipulado.Handlebars.compile() sobre el objeto AST controlado por el atacante.render() desencadena la ejecución de JavaScript arbitrario en el proceso del servidor.Cuando Handlebars se implementa en un entorno backend de Node.js, esta vulnerabilidad conduce a una vía de ejecución de código del lado del servidor, a diferencia del XSS estándar del lado del cliente. El ataque no requiere que se refleje ninguna salida al usuario; la carga útil se ejecuta dentro del proceso Node.js con los mismos privilegios que la aplicación. Dependiendo del despliegue:
/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) |
+-------------------------------------------+
Actualiza Handlebars.js a 4.7.9 o superior.
La versión 4.7.9 introduce una validación estricta del tipo de entrada en compile(), rechazando argumentos que no sean cadenas antes de que comience la generación de código.
Aplica una verificación de tipo antes de llamar a compile().
Si no es posible una actualización inmediata, valida en el punto de llamada que el argumento sea siempre una string:
if (typeof templateInput !== 'string') throw new TypeError('Template must be a string');
const render = Handlebars.compile(templateInput);
Usa handlebars/runtime para la precompilación en tiempo de compilación.
Precompila las plantillas en tiempo de compilación con la CLI de Handlebars y distribuye solo el paquete de runtime. La compilación de runtime no incluye compile(), eliminando por completo la superficie de ataque en los despliegues de producción.
Este repositorio está destinado exclusivamente a la investigación de seguridad, la educación y la demostración controlada de vulnerabilidades. No debe utilizarse para probar ni explotar sistemas sin la autorización escrita explícita del propietario del sistema. El propósito de este proyecto es ayudar a los investigadores de seguridad, defensores y desarrolladores a comprender la vulnerabilidad, validar la exposición en entornos de laboratorio controlados y aplicar mitigaciones efectivas.