[SECURITY] Prevent XSS in modal component 93/59093/2
authorFrank Naegler <frank.naegler@typo3.org>
Tue, 11 Dec 2018 09:56:10 +0000 (10:56 +0100)
committerOliver Hader <oliver.hader@typo3.org>
Tue, 11 Dec 2018 09:56:12 +0000 (10:56 +0100)
Resolves: #84190
Releases: master, 8.7, 7.6
Security-Commit: e991d9ac10b78f360bff386d9a822f0caa7c781d
Security-Bulletin: TYPO3-CORE-SA-2018-007
Change-Id: I41f0d6bdb5e06b6f08b19feaf59ea47e3a197549
Reviewed-on: https://review.typo3.org/59093
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/Private/TypeScript/SecurityUtility.ts [new file with mode: 0644]
typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js [new file with mode: 0644]

index 4a84ae2..039162a 100644 (file)
 define(['jquery',
   'TYPO3/CMS/Backend/Severity',
   'TYPO3/CMS/Backend/Icons',
+  'TYPO3/CMS/Core/SecurityUtility',
   'bootstrap'
-], function($, Severity, Icons) {
+], function($, Severity, Icons, SecurityUtility) {
   'use strict';
 
+  var securityUtility = new SecurityUtility();
+
   try {
     // fetch from parent
     if (parent && parent.window.TYPO3 && parent.window.TYPO3.Modal) {
@@ -233,7 +236,15 @@ define(['jquery',
     // Validation of configuration
     configuration.type = typeof configuration.type === 'string' && configuration.type in Modal.types ? configuration.type : Modal.defaultConfiguration.type;
     configuration.title = typeof configuration.title === 'string' ? configuration.title : Modal.defaultConfiguration.title;
-    configuration.content = typeof configuration.content === 'string' || typeof configuration.content === 'object' ? configuration.content : Modal.defaultConfiguration.content;
+    if (typeof configuration.content === 'string') {
+      // A string means, no markup allowed, let's ensure this
+      configuration.content = securityUtility.encodeHtml(configuration.content);
+    } else if (typeof configuration.content === 'object') {
+      // An object means, a valid jQuery object with markup, let's get the markup
+      configuration.content = configuration.content.html();
+    } else {
+      configuration.content = Modal.defaultConfiguration.content;
+    }
     configuration.severity = typeof configuration.severity !== 'undefined' ? configuration.severity : Modal.defaultConfiguration.severity;
     configuration.buttons = configuration.buttons || Modal.defaultConfiguration.buttons;
     configuration.size = typeof configuration.size === 'string' && configuration.size in Modal.sizes ? configuration.size : Modal.defaultConfiguration.size;
@@ -318,10 +329,7 @@ define(['jquery',
       if (typeof content === 'object') {
         currentModal.find(Modal.identifiers.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 />').html(content);
-        }
+        content = $('<p />').html(content);
         currentModal.find(Modal.identifiers.body).html(content);
       }
     }
diff --git a/typo3/sysext/core/Resources/Private/TypeScript/SecurityUtility.ts b/typo3/sysext/core/Resources/Private/TypeScript/SecurityUtility.ts
new file mode 100644 (file)
index 0000000..99d6559
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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
+ * contains method to escape input to prevent XSS and other security related things
+ * @exports TYPO3/CMS/Core/SecurityUtility
+ */
+class SecurityUtility {
+  private readonly documentRef: Document;
+
+  /**
+   * @param {Document} documentRef
+   */
+  constructor(documentRef: Document = 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}
+   */
+  public encodeHtml(value: string, doubleEncode: boolean = true): string {
+    let anvil: HTMLSpanElement = 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, (html: string) => {
+        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;
+  }
+
+  /**
+   * @param {string} value
+   */
+  public debug(value: string): void {
+    if (value !== this.encodeHtml(value)) {
+      console.warn('XSS?!', value);
+    }
+  }
+
+  /**
+   * @return {HTMLSpanElement}
+   */
+  private createAnvil(): HTMLSpanElement {
+    return this.documentRef.createElement('span');
+  }
+}
+
+export = SecurityUtility;
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..459e8fc
--- /dev/null
@@ -0,0 +1,13 @@
+/*
+ * 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!
+ */
+define(["require","exports"],function(e,n){"use strict";return function(){function e(e){void 0===e&&(e=document),this.documentRef=e}return e.prototype.encodeHtml=function(e,n){void 0===n&&(n=!0);var t=this.createAnvil();return n||(e=e.replace(/&[#A-Za-z0-9]+;/g,function(e){return t.innerHTML=e,t.innerText})),t.innerText=e,t.innerHTML},e.prototype.createAnvil=function(){return this.documentRef.createElement("span")},e.prototype.debug=function(e){e!==this.encodeHtml(e)&&console.warn("XSS?!",e)},e}()});
\ No newline at end of file