From 89a38ad0ef9411745954f53f29bea5b8ce81cd32 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Tue, 11 Dec 2018 10:55:11 +0100 Subject: [PATCH] [SECURITY] Prevent XSS in modal component Resolves: #84190 Releases: master, 8.7, 7.6 Security-Commit: 4e75300bebae5e06887f3234a32a0bae9635c047 Security-Bulletin: TYPO3-CORE-SA-2018-007 Change-Id: I29ca9803823825066af87b2534aaf407183c1b4e Reviewed-on: https://review.typo3.org/59085 Reviewed-by: Oliver Hader Tested-by: Oliver Hader --- .../Resources/Public/JavaScript/Modal.js | 12 +-- .../Public/JavaScript/SecurityUtility.js | 78 +++++++++++++++++++ 2 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js b/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js index dd57af51ecb6..33aef80825ad 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js @@ -17,10 +17,13 @@ */ define(['jquery', 'TYPO3/CMS/Backend/Severity', + 'TYPO3/CMS/Core/SecurityUtility', 'bootstrap' - ], function($, Severity) { + ], function($, Severity, SecurityUtility) { 'use strict'; + var securityUtility = new SecurityUtility(); + try { // fetch from parent if (parent && parent.window.TYPO3 && parent.window.TYPO3.Modal) { @@ -196,10 +199,9 @@ define(['jquery', if (typeof content === 'object') { currentModal.find('.modal-body').append(content); } else { - // we need html, check if we have to wrap content in

- if (!/^<[a-z][\s\S]*>/i.test(content)) { - content = $('

').text(content); - } + content = $('

').html( + securityUtility.encodeHtml(content) + ); currentModal.find('.modal-body').html(content); } diff --git a/typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js b/typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js new file mode 100644 index 000000000000..6ef4ede1408e --- /dev/null +++ b/typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js @@ -0,0 +1,78 @@ +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +/** + * Module: TYPO3/CMS/Core/SecurityUtility + */ +define([], function() { + 'use strict'; + + /** + * Module: TYPO3/CMS/Core/SecurityUtility + * contains method to escape input to prevent XSS and other security related things + * @exports TYPO3/CMS/Core/SecurityUtility + */ + var SecurityUtility = (function() { + /** + * @param {Document} documentRef + */ + function SecurityUtility(documentRef) { + if (documentRef === void 0) { + documentRef = document; + } + this.documentRef = documentRef; + } + /** + * Encodes HTML to use according entities. Behavior is similar to PHP's + * htmlspecialchars. Input might contain XSS, output has it encoded. + * + * @param {string} value Input value to be encoded + * @param {boolean} doubleEncode (default `true`) + * @return {string} + */ + SecurityUtility.prototype.encodeHtml = function(value, doubleEncode) { + if (doubleEncode === void 0) { + doubleEncode = true; + } + var anvil = this.createAnvil(); + if (!doubleEncode) { + // decode HTML entities step-by-step + // but NEVER(!) as a whole, since that would allow XSS + value = value.replace(/&[#A-Za-z0-9]+;/g, function (html) { + anvil.innerHTML = html; + return anvil.innerText; + }); + } + // apply arbitrary data a text node + // thus browser is capable of properly encoding + anvil.innerText = value; + return anvil.innerHTML; + }; + /** + * @return {HTMLSpanElement} + */ + SecurityUtility.prototype.createAnvil = function() { + return this.documentRef.createElement('span'); + }; + /** + * @param {string} value + */ + SecurityUtility.prototype.debug = function(value) { + if (value !== this.encodeHtml(value)) { + console.warn('XSS?!', value); + } + }; + return SecurityUtility; + }()); + return SecurityUtility; +});