diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index cfe1e917..5342f294 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -212,7 +212,13 @@ function registerDefaultHelpers(instance) { }); instance.registerHelper('lookup', function(obj, field) { - return obj && obj[field]; + if (!obj) { + return obj; + } + if (field === 'constructor' && !obj.propertyIsEnumerable(field)) { + return undefined; + } + return obj[field]; }); } diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 88306616..f070a39f 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -13,6 +13,9 @@ 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 (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { return [parent, '.', name]; } else { diff --git a/spec/security.js b/spec/security.js new file mode 100644 index 00000000..5af5334e --- /dev/null +++ b/spec/security.js @@ -0,0 +1,33 @@ +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"}}', {}, ''); + }); + + 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'); + 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() { + } + + Object.defineProperty(TestClass.prototype, 'abc', { + get: function() { + return 'xyz'; + } + }); + + shouldCompileTo('{{#with this}}{{this.abc}}{{/with}}', + new TestClass(), 'xyz'); + shouldCompileTo('{{#with this}}{{lookup this "abc"}}{{/with}}', + new TestClass(), 'xyz'); + }); + }); +});