diff --git a/InvenTree/InvenTree/static/script/inventree/inventree.js b/InvenTree/InvenTree/static/script/inventree/inventree.js index 7999e4a7be..92239cec86 100644 --- a/InvenTree/InvenTree/static/script/inventree/inventree.js +++ b/InvenTree/InvenTree/static/script/inventree/inventree.js @@ -13,6 +13,7 @@ inventreeDocReady, inventreeLoad, inventreeSave, + sanitizeData, */ function attachClipboard(selector, containerselector, textElement) { @@ -273,6 +274,40 @@ function loadBrandIcon(element, name) { } } + +/* + * Function to sanitize a (potentially nested) object. + * Iterates through all levels, and sanitizes each primitive string. + * + * Note that this function effectively provides a "deep copy" of the provided data, + * and the original data structure is unaltered. + */ +function sanitizeData(data) { + if (data == null) { + return null; + } else if (Array.isArray(data)) { + // Handle arrays + var ret = []; + data.forEach(function(val) { + ret.push(sanitizeData(val)); + }); + } else if (typeof(data) === 'object') { + // Handle nested structures + var nested = {}; + $.each(data, function(k, v) { + nested[k] = sanitizeData(v); + }); + + return nested; + } else if (typeof(data) === 'string') { + // Perform string replacement + return data.replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/`/g, '`'); + } else { + return data; + } +} + + // Convenience function to determine if an element exists $.fn.exists = function() { return this.length !== 0; diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index b4d5fe6cdc..8c6c79843d 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -204,6 +204,9 @@ function constructChangeForm(fields, options) { }, success: function(data) { + // Ensure the data are fully sanitized before we operate on it + data = sanitizeData(data); + // An optional function can be provided to process the returned results, // before they are rendered to the form if (options.processResults) { diff --git a/InvenTree/templates/js/translated/tables.js b/InvenTree/templates/js/translated/tables.js index 2e40a6bbc5..c933acc743 100644 --- a/InvenTree/templates/js/translated/tables.js +++ b/InvenTree/templates/js/translated/tables.js @@ -289,6 +289,8 @@ $.fn.inventreeTable = function(options) { // Extract query params var filters = options.queryParams || options.filters || {}; + options.escape = true; + // Store the total set of query params options.query_params = filters; @@ -475,6 +477,49 @@ function customGroupSorter(sortName, sortOrder, sortData) { $.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales['en-US-custom']); + // Enable HTML escaping by default + $.fn.bootstrapTable.escape = true; + + // Override the 'calculateObjectValue' function at bootstrap-table.js:3525 + // Allows us to escape any nasty HTML tags which are rendered to the DOM + $.fn.bootstrapTable.utils._calculateObjectValue = $.fn.bootstrapTable.utils.calculateObjectValue; + + $.fn.bootstrapTable.utils.calculateObjectValue = function escapeCellValue(self, name, args, defaultValue) { + + var args_list = []; + + if (args) { + + args_list.push(args[0]); + + if (name && typeof(name) === 'function' && name.name == 'formatter') { + /* This is a custom "formatter" function for a particular cell, + * which may side-step regular HTML escaping, and inject malicious code into the DOM. + * + * Here we have access to the 'args' supplied to the custom 'formatter' function, + * which are in the order: + * args = [value, row, index, field] + * + * 'row' is the one we are interested in + */ + + var row = Object.assign({}, args[1]); + + args_list.push(sanitizeData(row)); + } else { + args_list.push(args[1]); + } + + for (var ii = 2; ii < args.length; ii++) { + args_list.push(args[ii]); + } + } + + var value = $.fn.bootstrapTable.utils._calculateObjectValue(self, name, args_list, defaultValue); + + return value; + }; + })(jQuery); $.extend($.fn.treegrid.defaults, {