MDL-62284 javascript: prevent nested JS mustache handler calls

Prevent the JS mustache helper from being executed from within
the render call of another mustache helper because it can allow
users to inject JS into the page.
This commit is contained in:
Ryan Wyllie
2019-09-04 11:24:34 +08:00
committed by Andrew Nicols
parent 3cf2cce110
commit d5ac2c69cf
2 changed files with 64 additions and 19 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+63 -18
View File
@@ -59,6 +59,9 @@ define([
/** @var {Object} iconSystem - Object extending core/iconsystem */
var iconSystem = {};
/** @var {Array} blacklistedNestedHelpers - List of helpers that can't be called within other helpers */
var blacklistedNestedHelpers = ['js'];
/**
* Constructor
*
@@ -357,6 +360,60 @@ define([
return '[[_t_' + index + ']]';
};
/**
* Return a helper function to be added to the context for rendering the a
* template.
*
* This will parse the provided text before giving it to the helper function
* in order to remove any blacklisted nested helpers to prevent one helper
* from calling another.
*
* In particular to prevent the JS helper from being called from within another
* helper because it can lead to security issues when the JS portion is user
* provided.
*
* @param {function} helperFunction The helper function to add
* @param {object} context The template context for the helper function
* @return {Function} To be set in the context
*/
Renderer.prototype.addHelperFunction = function(helperFunction, context) {
return function() {
return function(sectionText, helper) {
// Override the blacklisted helpers in the template context with
// a function that returns an empty string for use when executing
// other helpers. This is to prevent these helpers from being
// executed as part of the rendering of another helper in order to
// prevent any potential security issues.
var originalHelpers = blacklistedNestedHelpers.reduce(function(carry, name) {
if (context.hasOwnProperty(name)) {
carry[name] = context[name];
}
return carry;
}, {});
blacklistedNestedHelpers.forEach(function(helperName) {
context[helperName] = function() {
return '';
};
});
// Execute the helper with the modified context that doesn't include
// the blacklisted nested helpers. This prevents the blacklisted
// helpers from being called from within other helpers.
var result = helperFunction.apply(this, [context, sectionText, helper]);
// Restore the original helper implementation in the context so that
// any further rendering has access to them again.
for (var name in originalHelpers) {
context[name] = originalHelpers[name];
}
return result;
}.bind(this);
}.bind(this);
};
/**
* Add some common helper functions to all context objects passed to templates.
* These helpers match exactly the helpers available in php.
@@ -371,24 +428,12 @@ define([
this.requiredStrings = [];
this.requiredJS = [];
context.uniqid = (uniqInstances++);
context.str = function() {
return this.stringHelper.bind(this, context);
}.bind(this);
context.pix = function() {
return this.pixHelper.bind(this, context);
}.bind(this);
context.js = function() {
return this.jsHelper.bind(this, context);
}.bind(this);
context.quote = function() {
return this.quoteHelper.bind(this, context);
}.bind(this);
context.shortentext = function() {
return this.shortenTextHelper.bind(this, context);
}.bind(this);
context.userdate = function() {
return this.userDateHelper.bind(this, context);
}.bind(this);
context.str = this.addHelperFunction(this.stringHelper, context);
context.pix = this.addHelperFunction(this.pixHelper, context);
context.js = this.addHelperFunction(this.jsHelper, context);
context.quote = this.addHelperFunction(this.quoteHelper, context);
context.shortentext = this.addHelperFunction(this.shortenTextHelper, context);
context.userdate = this.addHelperFunction(this.userDateHelper, context);
context.globals = {config: config};
context.currentTheme = themeName;
};