VYPR
High severityNVD Advisory· Published Sep 30, 2020· Updated Aug 5, 2024

CVE-2019-20920

CVE-2019-20920

Description

Handlebars before 3.0.8 and 4.x before 4.5.3 is vulnerable to Arbitrary Code Execution. The lookup helper fails to properly validate templates, allowing attackers to submit templates that execute arbitrary JavaScript. This can be used to run arbitrary code on a server processing Handlebars templates or in a victim's browser (effectively serving as XSS).

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Handlebars before 3.0.8 and 4.x before 4.5.3 allows arbitrary code execution via the lookup helper, enabling server-side RCE or XSS.

Vulnerability

Overview Handlebars, a popular JavaScript templating engine, is vulnerable to arbitrary code execution (ACE) in versions prior to 3.0.8 and 4.x prior to 4.5.3. The flaw resides in the lookup helper, which fails to properly validate property names when accessing object properties. Specifically, the helper does not block access to the constructor property or other dangerous built-in properties, allowing an attacker to traverse the prototype chain and execute arbitrary JavaScript [1][2][4].

Exploitation

Details An attacker can submit a crafted Handlebars template that leverages the lookup helper to access constructor, toString, or other prototype properties. By constructing a chain like {{lookup (lookup this "constructor") "name"}} or using similar techniques, the attacker can invoke arbitrary functions. The proof-of-concept (PoC) provided in the Snyk advisory demonstrates how to execute alert('Vulnerable Handlebars JS'); in the context of the application [2][4]. This can be exploited in two primary scenarios: on the server side, where templates are processed by Node.js, leading to remote code execution (RCE); or in the browser, where the template output is rendered, effectively serving as stored or reflected cross-site scripting (XSS) [1].

Impact

Successful exploitation allows an attacker to execute arbitrary JavaScript with the privileges of the application. On a server, this can lead to full system compromise, data exfiltration, or lateral movement. In a client-side context, the attacker can steal session cookies, deface web pages, or perform actions on behalf of the victim user [1][4].

Mitigation

The vulnerability is fixed in Handlebars versions 3.0.8 and 4.5.3 and later. Users should upgrade their handlebars dependency immediately. The fix introduces a dangerousPropertyRegex to filter out access to dangerous properties like constructor and __proto__, and modifies the nameLookup function in the compiler to check for own properties before allowing access [3]. No workarounds are documented; upgrading is the recommended action [4].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
handlebarsnpm
< 3.0.83.0.8
handlebarsnpm
>= 4.0.0, < 4.5.34.5.3

Affected products

114

Patches

4
16bd606fec0a

v3.0.8

https://github.com/handlebars-lang/handlebars.jsNils KnappmeierFeb 23, 2020via osv
4 files changed · +4 4
  • components/bower.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "handlebars",
    -  "version": "3.0.7",
    +  "version": "3.0.8",
       "main": "handlebars.js",
       "dependencies": {}
     }
    
  • components/handlebars.js.nuspec+1 1 modified
    @@ -2,7 +2,7 @@
     <package>
     	<metadata>
     		<id>handlebars.js</id>
    -		<version>3.0.7</version>
    +		<version>3.0.8</version>
     		<authors>handlebars.js Authors</authors>
     		<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
     		<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
    
  • components/package.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "handlebars",
    -  "version": "3.0.7",
    +  "version": "3.0.8",
       "license": "MIT",
       "jspm": {
         "main": "handlebars",
    
  • package.json+1 1 modified
    @@ -1,7 +1,7 @@
     {
       "name": "handlebars",
       "barename": "handlebars",
    -  "version": "3.0.7",
    +  "version": "3.0.8",
       "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
       "homepage": "http://www.handlebarsjs.com/",
       "keywords": [
    
c819c8b53393

v4.5.3

https://github.com/handlebars-lang/handlebars.jsNils KnappmeierNov 18, 2019via osv
5 files changed · +5 5
  • components/bower.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "handlebars",
    -  "version": "4.5.2",
    +  "version": "4.5.3",
       "main": "handlebars.js",
       "license": "MIT",
       "dependencies": {}
    
  • components/handlebars.js.nuspec+1 1 modified
    @@ -2,7 +2,7 @@
     <package>
     	<metadata>
     		<id>handlebars.js</id>
    -		<version>4.5.2</version>
    +		<version>4.5.3</version>
     		<authors>handlebars.js Authors</authors>
     		<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
     		<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
    
  • components/package.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "handlebars",
    -  "version": "4.5.2",
    +  "version": "4.5.3",
       "license": "MIT",
       "jspm": {
         "main": "handlebars",
    
  • lib/handlebars/base.js+1 1 modified
    @@ -4,7 +4,7 @@ import {registerDefaultHelpers} from './helpers';
     import {registerDefaultDecorators} from './decorators';
     import logger from './logger';
     
    -export const VERSION = '4.5.2';
    +export const VERSION = '4.5.3';
     export const COMPILER_REVISION = 8;
     export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
     
    
  • package.json+1 1 modified
    @@ -1,7 +1,7 @@
     {
       "name": "handlebars",
       "barename": "handlebars",
    -  "version": "4.5.2",
    +  "version": "4.5.3",
       "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
       "homepage": "http://www.handlebarsjs.com/",
       "keywords": [
    
156061eb7707

backport fixes from 4.x

https://github.com/handlebars-lang/handlebars.jsNils KnappmeierFeb 22, 2020via ghsa
4 files changed · +31 8
  • lib/handlebars/base.js+1 1 modified
    @@ -215,7 +215,7 @@ function registerDefaultHelpers(instance) {
         if (!obj) {
           return obj;
         }
    -    if (field === 'constructor' && !obj.propertyIsEnumerable(field)) {
    +    if (Utils.dangerousPropertyRegex.test(String(field)) && !Object.prototype.hasOwnProperty.call(obj, field)) {
           return undefined;
         }
         return obj[field];
    
  • lib/handlebars/compiler/javascript-compiler.js+12 7 modified
    @@ -1,6 +1,6 @@
     import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
     import Exception from '../exception';
    -import {isArray} from '../utils';
    +import {isArray, dangerousPropertyRegex} from '../utils';
     import CodeGen from './code-gen';
     
     function Literal(value) {
    @@ -13,13 +13,18 @@ JavaScriptCompiler.prototype = {
       // PUBLIC API: You can override these methods in a subclass to provide
       // alternative compiled forms for name lookup and buffering semantics
       nameLookup: function(parent, name /* , type*/) {
    -    if (name === 'constructor') {
    -      return ['(', parent, '.propertyIsEnumerable(\'constructor\') ? ', parent, '.constructor : undefined', ')'];
    +    if (dangerousPropertyRegex.test(name)) {
    +      const isOwnProperty = [ this.aliasable('Object.prototype.hasOwnProperty'), '.call(', parent, ',', JSON.stringify(name), ')'];
    +      return ['(', isOwnProperty, '?', _actualLookup(), ' : undefined)'];
         }
    -    if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
    -      return [parent, '.', name];
    -    } else {
    -      return [parent, "['", name, "']"];
    +    return _actualLookup();
    +
    +    function _actualLookup() {
    +      if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
    +        return [parent, '.', name];
    +      } else {
    +        return [parent, '[', JSON.stringify(name), ']'];
    +      }
         }
       },
       depthedLookup: function(name) {
    
  • lib/handlebars/utils.js+2 0 modified
    @@ -101,3 +101,5 @@ export function blockParams(params, ids) {
     export function appendContextPath(contextPath, id) {
       return (contextPath ? contextPath + '.' : '') + id;
     }
    +
    +export const dangerousPropertyRegex = /^(constructor|__defineGetter__|__defineSetter__|__lookupGetter__|__proto__)$/;
    
  • spec/security.js+16 0 modified
    @@ -30,4 +30,20 @@ describe('security issues', function() {
                     new TestClass(), 'xyz');
             });
         });
    +
    +    describe('GH-1595', function() {
    +        it('properties, that are required to be enumerable', function() {
    +            shouldCompileTo('{{constructor.name}}', {}, '');
    +            shouldCompileTo('{{__defineGetter__.name}}', {}, '');
    +            shouldCompileTo('{{__defineSetter__.name}}', {}, '');
    +            shouldCompileTo('{{__lookupGetter__.name}}', {}, '');
    +            shouldCompileTo('{{__proto__.__defineGetter__.name}}', {}, '');
    +
    +            shouldCompileTo('{{lookup this "constructor"}}', {}, '');
    +            shouldCompileTo('{{lookup this "__defineGetter__"}}', {}, '');
    +            shouldCompileTo('{{lookup this "__defineSetter__"}}', {}, '');
    +            shouldCompileTo('{{lookup this "__lookupGetter__"}}', {}, '');
    +            shouldCompileTo('{{lookup this "__proto__"}}', {}, '');
    +        });
    +    });
     });
    
d54137810a49

fix: use String(field) in lookup when checking for "constructor"

https://github.com/handlebars-lang/handlebars.jsNils KnappmeierNov 13, 2019via ghsa
2 files changed · +26 4
  • lib/handlebars/helpers/lookup.js+1 1 modified
    @@ -3,7 +3,7 @@ export default function(instance) {
         if (!obj) {
           return obj;
         }
    -    if (field === 'constructor' && !obj.propertyIsEnumerable(field)) {
    +    if (String(field) === 'constructor' && !obj.propertyIsEnumerable(field)) {
           return undefined;
         }
         return obj[field];
    
  • spec/security.js+25 3 modified
    @@ -1,11 +1,26 @@
     describe('security issues', function() {
         describe('GH-1495: Prevent Remote Code Execution via constructor', function() {
             it('should not allow constructors to be accessed', function() {
    -            shouldCompileTo('{{constructor.name}}', {}, '');
    -            shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {}, '');
    +            expectTemplate('{{lookup (lookup this "constructor") "name"}}')
    +                .withInput({})
    +                .toCompileTo('');
    +
    +            expectTemplate('{{constructor.name}}')
    +                .withInput({})
    +                .toCompileTo('');
             });
     
    -        it('should allow the "constructor" property to be accessed if it is enumerable', function() {
    +        it('GH-1603: should not allow constructors to be accessed (lookup via toString)', function() {
    +          expectTemplate('{{lookup (lookup this (list "constructor")) "name"}}')
    +              .withInput({})
    +              .withHelper('list', function(element) {
    +                return [element];
    +              })
    +              .toCompileTo('');
    +        });
    +
    +
    +      it('should allow the "constructor" property to be accessed if it is enumerable', function() {
                 shouldCompileTo('{{constructor.name}}', {'constructor': {
                     'name': 'here we go'
                 }}, 'here we go');
    @@ -14,6 +29,13 @@ describe('security issues', function() {
                 }}, 'here we go');
             });
     
    +        it('should allow the "constructor" property to be accessed if it is enumerable', function() {
    +            shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {'constructor': {
    +                    'name': 'here we go'
    +                }}, 'here we go');
    +        });
    +
    +
             it('should allow prototype properties that are not constructors', function() {
                 function TestClass() {
                 }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.