[SECURITY] Prevent XSS in modal component 85/59085/2
authorOliver Hader <oliver@typo3.org>
Tue, 11 Dec 2018 09:55:11 +0000 (10:55 +0100)
committerOliver Hader <oliver.hader@typo3.org>
Tue, 11 Dec 2018 09:55:12 +0000 (10:55 +0100)
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 <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js [new file with mode: 0644]

index dd57af5..33aef80 100644 (file)
  */
 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 <p>
-                       if (!/^<[a-z][\s\S]*>/i.test(content)) {
-                               content = $('<p />').text(content);
-                       }
+                       content = $('<p />').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 (file)
index 0000000..6ef4ede
--- /dev/null
@@ -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;
+});