Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
describe('security issues', function () {
describe('GH-1495: Prevent Remote Code Execution via constructor', function () {
it('should not allow constructors to be accessed', function () {
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{constructor.name}}').withInput({}).toCompileTo('');
});
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 an "ownProperty"', function () {
expectTemplate('{{constructor.name}}')
.withInput({ constructor: { name: 'here we go' } })
.toCompileTo('here we go');
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
.withInput({ constructor: { name: 'here we go' } })
.toCompileTo('here we go');
});
it('should allow the "constructor" property to be accessed if it is an "own property"', function () {
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
.withInput({ constructor: { name: 'here we go' } })
.toCompileTo('here we go');
});
});
describe('GH-1558: Prevent explicit call of helperMissing-helpers', function () {
if (!Handlebars.compile) {
return;
}
describe('without the option "allowExplicitCallOfHelperMissing"', function () {
it('should throw an exception when calling "{{helperMissing}}" ', function () {
expectTemplate('{{helperMissing}}').toThrow(Error);
});
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function () {
expectTemplate('{{#helperMissing}}{{/helperMissing}}').toThrow(Error);
});
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function () {
var functionCalls = [];
expect(function () {
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template({
fn: function () {
functionCalls.push('called');
},
});
}).to.throw(Error);
expect(functionCalls.length).to.equal(0);
});
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function () {
expectTemplate('{{#blockHelperMissing .}}{{/blockHelperMissing}}')
.withInput({
fn: function () {
return 'functionInData';
},
})
.toThrow(Error);
});
});
describe('with the option "allowCallsToHelperMissing" set to true', function () {
it('should not throw an exception when calling "{{helperMissing}}" ', function () {
var template = Handlebars.compile('{{helperMissing}}');
template({}, { allowCallsToHelperMissing: true });
});
it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function () {
var template = Handlebars.compile(
'{{#helperMissing}}{{/helperMissing}}'
);
template({}, { allowCallsToHelperMissing: true });
});
it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function () {
var functionCalls = [];
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template(
{
fn: function () {
functionCalls.push('called');
},
},
{ allowCallsToHelperMissing: true }
);
equals(functionCalls.length, 1);
});
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function () {
var template = Handlebars.compile(
'{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}'
);
template({}, { allowCallsToHelperMissing: true });
});
});
});
describe('GH-1563', function () {
it('should not allow to access constructor after overriding via __defineGetter__', function () {
if ({}.__defineGetter__ == null || {}.__lookupGetter__ == null) {
return this.skip(); // Browser does not support this exploit anyway
}
expectTemplate(
'{{__defineGetter__ "undefined" valueOf }}' +
'{{#with __lookupGetter__ }}' +
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
'{{constructor.name}}' +
'{{/with}}'
)
.withInput({})
.toThrow(/Missing helper: "__defineGetter__"/);
});
});
describe('GH-1595: dangerous properties', function () {
var templates = [
'{{constructor}}',
'{{__defineGetter__}}',
'{{__defineSetter__}}',
'{{__lookupGetter__}}',
'{{__proto__}}',
'{{lookup this "constructor"}}',
'{{lookup this "__defineGetter__"}}',
'{{lookup this "__defineSetter__"}}',
'{{lookup this "__lookupGetter__"}}',
'{{lookup this "__proto__"}}',
];
templates.forEach(function (template) {
describe('access should be denied to ' + template, function () {
it('by default', function () {
expectTemplate(template).withInput({}).toCompileTo('');
});
it(' with proto-access enabled', function () {
expectTemplate(template)
.withInput({})
.withRuntimeOptions({
allowProtoPropertiesByDefault: true,
allowProtoMethodsByDefault: true,
})
.toCompileTo('');
});
});
});
});
describe('GH-1631: disallow access to prototype functions', function () {
function TestClass() {}
TestClass.prototype.aProperty = 'propertyValue';
TestClass.prototype.aMethod = function () {
return 'returnValue';
};
beforeEach(function () {
handlebarsEnv.resetLoggedPropertyAccesses();
});
afterEach(function () {
sinon.restore();
});
describe('control access to prototype methods via "allowedProtoMethods"', function () {
checkProtoMethodAccess({});
describe('in compat mode', function () {
checkProtoMethodAccess({ compat: true });
});
function checkProtoMethodAccess(compileOptions) {
it('should be prohibited by default and log a warning', function () {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.toCompileTo('');
expect(spy.calledOnce).to.be.true();
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
});
it('should only log the warning once', function () {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.toCompileTo('');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.toCompileTo('');
expect(spy.calledOnce).to.be.true();
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
});
it('can be allowed, which disables the warning', function () {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowedProtoMethods: {
aMethod: true,
},
})
.toCompileTo('returnValue');
expect(spy.callCount).to.equal(0);
});
it('can be turned on by default, which disables the warning', function () {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoMethodsByDefault: true,
})
.toCompileTo('returnValue');
expect(spy.callCount).to.equal(0);
});
it('can be turned off by default, which disables the warning', function () {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoMethodsByDefault: false,
})
.toCompileTo('');
expect(spy.callCount).to.equal(0);
});
it('can be turned off, if turned on by default', function () {
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoMethodsByDefault: true,
allowedProtoMethods: {
aMethod: false,
},
})
.toCompileTo('');
});
}
it('should cause the recursive lookup by default (in "compat" mode)', function () {
expectTemplate('{{#aString}}{{trim}}{{/aString}}')
.withInput({ aString: ' abc ', trim: 'trim' })
.withCompileOptions({ compat: true })
.toCompileTo('trim');
});
it('should not cause the recursive lookup if allowed through options(in "compat" mode)', function () {
expectTemplate('{{#aString}}{{trim}}{{/aString}}')
.withInput({ aString: ' abc ', trim: 'trim' })
.withCompileOptions({ compat: true })
.withRuntimeOptions({
allowedProtoMethods: {
trim: true,
},
})
.toCompileTo('abc');
});
});
describe('control access to prototype non-methods via "allowedProtoProperties" and "allowProtoPropertiesByDefault', function () {
checkProtoPropertyAccess({});
describe('in compat-mode', function () {
checkProtoPropertyAccess({ compat: true });
});
describe('in strict-mode', function () {
checkProtoPropertyAccess({ strict: true });
});
function checkProtoPropertyAccess(compileOptions) {
it('should be prohibited by default and log a warning', function () {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.toCompileTo('');
expect(spy.calledOnce).to.be.true();
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
});
it('can be explicitly prohibited by default, which disables the warning', function () {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoPropertiesByDefault: false,
})
.toCompileTo('');
expect(spy.callCount).to.equal(0);
});
it('can be turned on, which disables the warning', function () {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowedProtoProperties: {
aProperty: true,
},
})
.toCompileTo('propertyValue');
expect(spy.callCount).to.equal(0);
});
it('can be turned on by default, which disables the warning', function () {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoPropertiesByDefault: true,
})
.toCompileTo('propertyValue');
expect(spy.callCount).to.equal(0);
});
it('can be turned off, if turned on by default', function () {
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoPropertiesByDefault: true,
allowedProtoProperties: {
aProperty: false,
},
})
.toCompileTo('');
});
}
});
describe('compatibility with old runtimes, that do not provide the function "container.lookupProperty"', function () {
beforeEach(function simulateRuntimeWithoutLookupProperty() {
var oldTemplateMethod = handlebarsEnv.template;
sinon.replace(handlebarsEnv, 'template', function (templateSpec) {
templateSpec.main = wrapToAdjustContainer(templateSpec.main);
return oldTemplateMethod.call(this, templateSpec);
});
});
afterEach(function () {
sinon.restore();
});
it('should work with simple properties', function () {
expectTemplate('{{aProperty}}')
.withInput({ aProperty: 'propertyValue' })
.toCompileTo('propertyValue');
});
it('should work with Array.prototype.length', function () {
expectTemplate('{{anArray.length}}')
.withInput({ anArray: ['a', 'b', 'c'] })
.toCompileTo('3');
});
});
});
describe('escapes template variables', function () {
it('in compat mode', function () {
expectTemplate("{{'a\\b'}}")
.withCompileOptions({ compat: true })
.withInput({ 'a\\b': 'c' })
.toCompileTo('c');
});
it('in default mode', function () {
expectTemplate("{{'a\\b'}}")
.withCompileOptions()
.withInput({ 'a\\b': 'c' })
.toCompileTo('c');
});
it('in default mode', function () {
expectTemplate("{{'a\\b'}}")
.withCompileOptions({ strict: true })
.withInput({ 'a\\b': 'c' })
.toCompileTo('c');
});
});
});
function wrapToAdjustContainer(precompiledTemplateFunction) {
return function templateFunctionWrapper(container /*, more args */) {
delete container.lookupProperty;
return precompiledTemplateFunction.apply(this, arguments);
};
}