Use Object.prototype.propertyIsEnumerable to check for constructors

- context.propertyIsEnumerable can be replaced
  via __definedGetter__
- This is a fix specific to counter a known RCE exploit.
  Other fixes will follow.

closes #1563
This commit is contained in:
Nils Knappmeier
2019-09-26 23:55:14 +02:00
parent 050cca0866
commit 213c0bbe3c
2 changed files with 33 additions and 15 deletions
+22 -14
View File
@@ -14,12 +14,20 @@ JavaScriptCompiler.prototype = {
// alternative compiled forms for name lookup and buffering semantics // alternative compiled forms for name lookup and buffering semantics
nameLookup: function(parent, name/* , type*/) { nameLookup: function(parent, name/* , type*/) {
if (name === 'constructor') { if (name === 'constructor') {
return ['(', parent, '.propertyIsEnumerable(\'constructor\') ? ', parent, '.constructor : undefined', ')']; return ['(', _isEnumerable(), '?', _actualLookup(), ' : undefined)'];
} }
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { return _actualLookup();
return [parent, '.', name];
} else { function _isEnumerable() {
return [parent, '[', JSON.stringify(name), ']']; return `Object.prototype.propertyIsEnumerable.call(${parent},'constructor')`;
}
function _actualLookup() {
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
return [parent, '.', name];
} else {
return [parent, '[', JSON.stringify(name), ']'];
}
} }
}, },
depthedLookup: function(name) { depthedLookup: function(name) {
@@ -339,9 +347,9 @@ JavaScriptCompiler.prototype = {
params.splice(1, 0, current); params.splice(1, 0, current);
this.pushSource([ this.pushSource([
'if (!', this.lastHelper, ') { ', 'if (!', this.lastHelper, ') { ',
current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params), current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params),
'}']); '}']);
}, },
// [appendContent] // [appendContent]
@@ -686,16 +694,16 @@ JavaScriptCompiler.prototype = {
if (!this.options.strict) { if (!this.options.strict) {
lookup[0] = '(helper = '; lookup[0] = '(helper = ';
lookup.push( lookup.push(
' != null ? helper : ', ' != null ? helper : ',
this.aliasable('container.hooks.helperMissing') this.aliasable('container.hooks.helperMissing')
); );
} }
this.push([ this.push([
'(', lookup, '(', lookup,
(helper.paramsInit ? ['),(', helper.paramsInit] : []), '),', (helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
'(typeof helper === ', this.aliasable('"function"'), ' ? ', '(typeof helper === ', this.aliasable('"function"'), ' ? ',
this.source.functionCall('helper', 'call', helper.callParams), ' : helper))' this.source.functionCall('helper', 'call', helper.callParams), ' : helper))'
]); ]);
}, },
+11 -1
View File
@@ -33,7 +33,7 @@ describe('security issues', function() {
}); });
}); });
describe('GH-xxxx: Prevent explicit call of helperMissing-helpers', function() { describe('GH-1558: Prevent explicit call of helperMissing-helpers', function() {
if (!Handlebars.compile) { if (!Handlebars.compile) {
return; return;
} }
@@ -88,4 +88,14 @@ describe('security issues', function() {
}); });
}); });
}); });
describe('GH-1563', function() {
it('should not allow to access constructor after overriding via __defineGetter__', function() {
shouldCompileTo('{{__defineGetter__ "undefined" valueOf }}' +
'{{#with __lookupGetter__ }}' +
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
'{{constructor.name}}' +
'{{/with}}', {}, '');
});
});
}); });