Skip to content

Commit 1988878

Browse files
committed
fix: add more properties required to be enumerable
- __defineGetter__, __defineSetter__, __lookupGetter__, __proto__
1 parent 886ba86 commit 1988878

File tree

5 files changed

+49
-29
lines changed

5 files changed

+49
-29
lines changed

lib/handlebars/compiler/compiler.js

+2-13
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@ Compiler.prototype = {
5454

5555
options.blockParams = options.blockParams || [];
5656

57-
// These changes will propagate to the other compiler components
58-
let knownHelpers = options.knownHelpers;
59-
options.knownHelpers = {
57+
options.knownHelpers = extend(Object.create(null), {
6058
'helperMissing': true,
6159
'blockHelperMissing': true,
6260
'each': true,
@@ -65,15 +63,7 @@ Compiler.prototype = {
6563
'with': true,
6664
'log': true,
6765
'lookup': true
68-
};
69-
if (knownHelpers) {
70-
// the next line should use "Object.keys", but the code has been like this a long time and changing it, might
71-
// cause backwards-compatibility issues... It's an old library...
72-
// eslint-disable-next-line guard-for-in
73-
for (let name in knownHelpers) {
74-
this.options.knownHelpers[name] = knownHelpers[name];
75-
}
76-
}
66+
}, options.knownHelpers);
7767

7868
return this.accept(program);
7969
},
@@ -369,7 +359,6 @@ Compiler.prototype = {
369359
if (isEligible && !isHelper) {
370360
let name = sexpr.path.parts[0],
371361
options = this.options;
372-
373362
if (options.knownHelpers[name]) {
374363
isHelper = true;
375364
} else if (options.knownHelpersOnly) {

lib/handlebars/compiler/javascript-compiler.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
22
import Exception from '../exception';
33
import {isArray} from '../utils';
44
import CodeGen from './code-gen';
5+
import {dangerousPropertyRegex} from '../helpers/lookup';
56

67
function Literal(value) {
78
this.value = value;
@@ -13,9 +14,8 @@ JavaScriptCompiler.prototype = {
1314
// PUBLIC API: You can override these methods in a subclass to provide
1415
// alternative compiled forms for name lookup and buffering semantics
1516
nameLookup: function(parent, name/* , type*/) {
16-
const isEnumerable = [ this.aliasable('container.propertyIsEnumerable'), '.call(', parent, ',"constructor")'];
17-
18-
if (name === 'constructor') {
17+
if (dangerousPropertyRegex.test(name)) {
18+
const isEnumerable = [ this.aliasable('container.propertyIsEnumerable'), '.call(', parent, ',', JSON.stringify(name), ')'];
1919
return ['(', isEnumerable, '?', _actualLookup(), ' : undefined)'];
2020
}
2121
return _actualLookup();

lib/handlebars/helpers/lookup.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
export const dangerousPropertyRegex = /^(constructor|__defineGetter__|__defineSetter__|__lookupGetter__|__proto__)$/;
2+
13
export default function(instance) {
24
instance.registerHelper('lookup', function(obj, field) {
35
if (!obj) {
46
return obj;
57
}
6-
if (String(field) === 'constructor' && !obj.propertyIsEnumerable(field)) {
8+
if (dangerousPropertyRegex.test(String(field)) && !obj.propertyIsEnumerable(field)) {
79
return undefined;
810
}
911
return obj[field];

spec/env/common.js

+20-9
Original file line numberDiff line numberDiff line change
@@ -142,24 +142,35 @@ HandlebarsTestBench.prototype.withMessage = function(message) {
142142
};
143143

144144
HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
145+
expect(this._compileAndExeute()).to.equal(expectedOutputAsString);
146+
};
147+
148+
// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw)
149+
HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher, msg) {
145150
var self = this;
151+
expect(function() {
152+
self._compileAndExeute();
153+
}).to.throw(errorLike, errMsgMatcher, msg);
154+
};
155+
156+
HandlebarsTestBench.prototype._compileAndExeute = function() {
146157
var compile = Object.keys(this.partials).length > 0
147158
? CompilerContext.compileWithPartial
148159
: CompilerContext.compile;
149160

161+
var combinedRuntimeOptions = this._combineRuntimeOptions();
162+
163+
var template = compile(this.templateAsString, this.compileOptions);
164+
return template(this.input, combinedRuntimeOptions);
165+
};
166+
167+
HandlebarsTestBench.prototype._combineRuntimeOptions = function() {
168+
var self = this;
150169
var combinedRuntimeOptions = {};
151170
Object.keys(this.runtimeOptions).forEach(function(key) {
152171
combinedRuntimeOptions[key] = self.runtimeOptions[key];
153172
});
154173
combinedRuntimeOptions.helpers = this.helpers;
155174
combinedRuntimeOptions.partials = this.partials;
156-
157-
var template = compile(this.templateAsString, this.compileOptions);
158-
var output = template(this.input, combinedRuntimeOptions);
159-
160-
if (output !== expectedOutputAsString) {
161-
// Error message formatted so that IntelliJ-Idea shows "diff"-button
162-
// https://stackoverflow.com/a/10945655/4251384
163-
throw new AssertError(this.message + '\nexpected:' + expectedOutputAsString + 'but was:' + output);
164-
}
175+
return combinedRuntimeOptions;
165176
};

spec/security.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,31 @@ describe('security issues', function() {
114114
describe('GH-1563', function() {
115115
it('should not allow to access constructor after overriding via __defineGetter__', function() {
116116
if (({}).__defineGetter__ == null || ({}).__lookupGetter__ == null) {
117-
return; // Browser does not support this exploit anyway
117+
return this.skip(); // Browser does not support this exploit anyway
118118
}
119-
shouldCompileTo('{{__defineGetter__ "undefined" valueOf }}' +
119+
expectTemplate('{{__defineGetter__ "undefined" valueOf }}' +
120120
'{{#with __lookupGetter__ }}' +
121121
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
122122
'{{constructor.name}}' +
123-
'{{/with}}', {}, '');
123+
'{{/with}}')
124+
.withInput({})
125+
.toThrow(/Missing helper: "__defineGetter__"/);
124126
});
125127
});
128+
129+
describe('GH-1595', function() {
130+
it('properties, that are required to be enumerable', function() {
131+
expectTemplate('{{constructor}}').withInput({}).toCompileTo('');
132+
expectTemplate('{{__defineGetter__}}').withInput({}).toCompileTo('');
133+
expectTemplate('{{__defineSetter__}}').withInput({}).toCompileTo('');
134+
expectTemplate('{{__lookupGetter__}}').withInput({}).toCompileTo('');
135+
expectTemplate('{{__proto__}}').withInput({}).toCompileTo('');
136+
137+
expectTemplate('{{lookup "constructor"}}').withInput({}).toCompileTo('');
138+
expectTemplate('{{lookup "__defineGetter__"}}').withInput({}).toCompileTo('');
139+
expectTemplate('{{lookup "__defineSetter__"}}').withInput({}).toCompileTo('');
140+
expectTemplate('{{lookup "__lookupGetter__"}}').withInput({}).toCompileTo('');
141+
expectTemplate('{{lookup "__proto__"}}').withInput({}).toCompileTo('');
142+
});
143+
});
126144
});

0 commit comments

Comments
 (0)