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, '"') + .replace(/'/g, '''); } /** 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('<>"'&'); + }); }); 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,""").replace(/'/g,"'")}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("<>"'&")})})})); \ No newline at end of file -- GitLab