From 69af3c4ecbbe98074b73810112c0e65d56055302 Mon Sep 17 00:00:00 2001
From: Oliver Hader <oliver@typo3.org>
Date: Fri, 11 Dec 2020 15:44:05 +0100
Subject: [PATCH] [TASK] Harden client-side SecurityUtility.encodeHtml

Ensures client-side function `SecurityUtility.encodeHtml` behaves like
`htmlspecialchars(..., ENT_QUOTES)`. The function is used for complete
nodes only, but now could be used for parts as well.

Resolves: #93068
Releases: master, 10.4, 9.5
Change-Id: I74b09676d0fdb8ddf09e7fc639480742fe645e9b
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/67105
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Torben Hansen <derhansen@gmail.com>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Torben Hansen <derhansen@gmail.com>
Reviewed-by: Benni Mack <benni@typo3.org>
---
 .../core/Resources/Public/TypeScript/SecurityUtility.ts       | 4 +++-
 Build/Sources/TypeScript/core/Tests/SecurityUtilityTest.ts    | 4 ++++
 .../core/Resources/Public/JavaScript/SecurityUtility.js       | 2 +-
 typo3/sysext/core/Tests/JavaScript/SecurityUtilityTest.js     | 2 +-
 4 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/Build/Sources/TypeScript/core/Resources/Public/TypeScript/SecurityUtility.ts b/Build/Sources/TypeScript/core/Resources/Public/TypeScript/SecurityUtility.ts
index 5916f2e257c4..fc709ddd26c5 100644
--- a/Build/Sources/TypeScript/core/Resources/Public/TypeScript/SecurityUtility.ts
+++ b/Build/Sources/TypeScript/core/Resources/Public/TypeScript/SecurityUtility.ts
@@ -59,7 +59,9 @@ class SecurityUtility {
     // apply arbitrary data a text node
     // thus browser is capable of properly encoding
     anvil.innerText = value;
-    return anvil.innerHTML;
+    return anvil.innerHTML
+      .replace(/"/g, '&quot;')
+      .replace(/'/g, '&apos;');
   }
 
   /**
diff --git a/Build/Sources/TypeScript/core/Tests/SecurityUtilityTest.ts b/Build/Sources/TypeScript/core/Tests/SecurityUtilityTest.ts
index ca3306386ee0..512cb8b9c428 100644
--- a/Build/Sources/TypeScript/core/Tests/SecurityUtilityTest.ts
+++ b/Build/Sources/TypeScript/core/Tests/SecurityUtilityTest.ts
@@ -36,4 +36,8 @@ describe('TYPO3/CMS/Core/SecurityUtility', (): void => {
       expect(() => (new SecurityUtility()).getRandomHexValue(invalidLength)).toThrowError(SyntaxError);
     }
   });
+
+  it('encodes HTML', (): void => {
+    expect((new SecurityUtility).encodeHtml('<>"\'&')).toBe('&lt;&gt;&quot;&apos;&amp;');
+  });
 });
diff --git a/typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js b/typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js
index 0ccc45986cde..880a3740a6e7 100644
--- a/typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js
+++ b/typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require","exports"],(function(e,t){"use strict";return class{constructor(e=document){this.documentRef=e}getRandomHexValue(e){if(e<=0||e!==Math.ceil(e))throw new SyntaxError("Length must be a positive integer");const t=new Uint8Array(Math.ceil(e/2));return crypto.getRandomValues(t),Array.from(t).map(e=>e.toString(16).padStart(2,"0")).join("").substr(0,e)}encodeHtml(e,t=!0){let n=this.createAnvil();return t||(e=e.replace(/&[#A-Za-z0-9]+;/g,e=>(n.innerHTML=e,n.innerText))),n.innerText=e,n.innerHTML}debug(e){e!==this.encodeHtml(e)&&console.warn("XSS?!",e)}createAnvil(){return this.documentRef.createElement("span")}}}));
\ No newline at end of file
+define(["require","exports"],(function(e,t){"use strict";return class{constructor(e=document){this.documentRef=e}getRandomHexValue(e){if(e<=0||e!==Math.ceil(e))throw new SyntaxError("Length must be a positive integer");const t=new Uint8Array(Math.ceil(e/2));return crypto.getRandomValues(t),Array.from(t).map(e=>e.toString(16).padStart(2,"0")).join("").substr(0,e)}encodeHtml(e,t=!0){let r=this.createAnvil();return t||(e=e.replace(/&[#A-Za-z0-9]+;/g,e=>(r.innerHTML=e,r.innerText))),r.innerText=e,r.innerHTML.replace(/"/g,"&quot;").replace(/'/g,"&apos;")}debug(e){e!==this.encodeHtml(e)&&console.warn("XSS?!",e)}createAnvil(){return this.documentRef.createElement("span")}}}));
\ No newline at end of file
diff --git a/typo3/sysext/core/Tests/JavaScript/SecurityUtilityTest.js b/typo3/sysext/core/Tests/JavaScript/SecurityUtilityTest.js
index bc11df008874..bdd00fdcbf61 100644
--- a/typo3/sysext/core/Tests/JavaScript/SecurityUtilityTest.js
+++ b/typo3/sysext/core/Tests/JavaScript/SecurityUtilityTest.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require","exports","TYPO3/CMS/Core/SecurityUtility"],(function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),describe("TYPO3/CMS/Core/SecurityUtility",()=>{it("generates random hex value",()=>{for(let e of function*(){yield 1,yield 20,yield 39}()){const t=(new r).getRandomHexValue(e);expect(t.length).toBe(e)}}),it("throws SyntaxError on invalid length",()=>{for(let e of function*(){yield 0,yield-90,yield 10.3}())expect(()=>(new r).getRandomHexValue(e)).toThrowError(SyntaxError)})})}));
\ No newline at end of file
+define(["require","exports","TYPO3/CMS/Core/SecurityUtility"],(function(e,t,o){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),describe("TYPO3/CMS/Core/SecurityUtility",()=>{it("generates random hex value",()=>{for(let e of function*(){yield 1,yield 20,yield 39}()){const t=(new o).getRandomHexValue(e);expect(t.length).toBe(e)}}),it("throws SyntaxError on invalid length",()=>{for(let e of function*(){yield 0,yield-90,yield 10.3}())expect(()=>(new o).getRandomHexValue(e)).toThrowError(SyntaxError)}),it("encodes HTML",()=>{expect((new o).encodeHtml("<>\"'&")).toBe("&lt;&gt;&quot;&apos;&amp;")})})}));
\ No newline at end of file
-- 
GitLab