/* * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), * and may include modifications made by Elasticsearch B.V. * Elasticsearch B.V. licenses this file to you under the MIT License. * See `packages/kbn-handlebars/LICENSE` for more information. */ import Handlebars from '../..'; import { expectTemplate } from '../__jest__/test_bench'; describe('security issues', () => { describe('GH-1495: Prevent Remote Code Execution via constructor', () => { it('should not allow constructors to be accessed', () => { 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)', () => { 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"', () => { 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"', () => { 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', () => { describe('without the option "allowExplicitCallOfHelperMissing"', () => { it('should throw an exception when calling "{{helperMissing}}" ', () => { expectTemplate('{{helperMissing}}').toThrow(Error); }); it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', () => { expectTemplate('{{#helperMissing}}{{/helperMissing}}').toThrow(Error); }); it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', () => { const functionCalls = []; expect(() => { const template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); template({ fn() { functionCalls.push('called'); }, }); }).toThrow(Error); expect(functionCalls.length).toEqual(0); }); it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', () => { expectTemplate('{{#blockHelperMissing .}}{{/blockHelperMissing}}') .withInput({ fn() { return 'functionInData'; }, }) .toThrow(Error); }); }); }); describe('GH-1563', () => { it('should not allow to access constructor after overriding via __defineGetter__', () => { // @ts-expect-error if ({}.__defineGetter__ == null || {}.__lookupGetter__ == null) { return; // 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', () => { const templates = [ '{{constructor}}', '{{__defineGetter__}}', '{{__defineSetter__}}', '{{__lookupGetter__}}', '{{__proto__}}', '{{lookup this "constructor"}}', '{{lookup this "__defineGetter__"}}', '{{lookup this "__defineSetter__"}}', '{{lookup this "__lookupGetter__"}}', '{{lookup this "__proto__"}}', ]; templates.forEach((template) => { describe('access should be denied to ' + template, () => { it('by default', () => { expectTemplate(template).withInput({}).toCompileTo(''); }); }); }); }); describe('escapes template variables', () => { it('in default mode', () => { expectTemplate("{{'a\\b'}}").withCompileOptions().withInput({ 'a\\b': 'c' }).toCompileTo('c'); }); it('in strict mode', () => { expectTemplate("{{'a\\b'}}") .withCompileOptions({ strict: true }) .withInput({ 'a\\b': 'c' }) .toCompileTo('c'); }); }); });