CVE-2023-27472: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value:  quickentity-editor-next is an open source, system local, video game asset editor. In affected versions HTML tags in entity names are not sanitised (XSS vulnerability). Allows arbitrary code execution within the browser sandbox, among other things, simply from loading a file containing a script tag in any entity name. This issue has been patched in version 1.28.1 of the application. Users are advised to upgrade. There are no known workarounds for this vulnerability.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2023-03-06T19:15Z
Repository: https://github.com/atampy25/quickentity-editor-next
Language: Other
Files changed: src/lib/components/MinimalTree.svelte, src/lib/components/Tree.svelte, src/lib/utils.ts
Commit date: 2023-03-05 13:17:37 +1000
Commit message: Sanitise tree view text

Diff: diff --git a/src/lib/components/MinimalTree.svelte b/src/lib/components/MinimalTree.svelte
index 476d41c..7286fd5 100644
--- a/src/lib/components/MinimalTree.svelte
+++ b/src/lib/components/MinimalTree.svelte
@@ -2,11 +2,11 @@
 	import jQuery from "jquery"
 	import "jstree"
 	import "./treeview.css"
 
 	import type { Entity } from "$lib/quickentity-types"
-	import { getReferencedLocalEntity } from "$lib/utils"
+	import { getReferencedLocalEntity, sanitise } from "$lib/utils"
 
 	import { createEventDispatcher, onMount } from "svelte"
 	import { v4 } from "uuid"
 	import json from "$lib/json"
 	import isEqual from "lodash/isEqual"
@@ -71,11 +71,12 @@
 				themes: {
 					name: "default",
 					dots: true,
 					icons: true
 				},
-				check_callback: true
+				check_callback: true,
+				force_text: true
 			},
 			search: {
 				fuzzy: true,
 				show_only_matches: true,
 				close_opened_onclear: false,
@@ -153,22 +154,22 @@
 					entityData.factory == "[modules:/zentity.class].pc_entitytype" && reverseReferences[entityID].some((a) => a.type == "parent")
 						? "far fa-folder"
 						: icons.find((a) => entityData.factory.includes(a[0]))
 						? icons.find((a) => entityData.factory.includes(a[0]))![1]
 						: "far fa-file",
-				text: `${entityData.name} (ref ${entityID})`,
+				text: `${sanitise(entityData.name)} (ref ${entityID})`,
 				folder: entityData.factory == "[modules:/zentity.class].pc_entitytype" && reverseReferences[entityID].some((a) => a.type == "parent") // for sorting and stuff
 			})
 		}
 
 		let index = 0
 		for (let entry of entity.comments) {
 			tree.settings!.core.data.push({
 				id: "comment-" + index,
 				parent: getReferencedLocalEntity(entry.parent) || "#",
 				icon: "far fa-sticky-note",
-				text: entry.name + " (comment)",
+				text: sanitise(entry.name) + " (comment)",
 				folder: false // for sorting and stuff
 			})
 
 			index++
 		}
diff --git a/src/lib/components/Tree.svelte b/src/lib/components/Tree.svelte
index 657acbf..d3b345d 100644
--- a/src/lib/components/Tree.svelte
+++ b/src/lib/components/Tree.svelte
@@ -2,11 +2,11 @@
 	import jQuery from "jquery"
 	import "jstree"
 	import "./treeview.css"
 
 	import type { Entity, FullRef, Ref, RefWithConstantValue, SubEntity } from "$lib/quickentity-types"
-	import { changeReferenceToLocalEntity, genRandHex, getReferencedEntities, getReferencedExternalEntities, getReferencedLocalEntity, normaliseToHash, traverseEntityTree } from "$lib/utils"
+	import { changeReferenceToLocalEntity, genRandHex, getReferencedEntities, getReferencedExternalEntities, getReferencedLocalEntity, normaliseToHash, sanitise, traverseEntityTree } from "$lib/utils"
 
 	import { createEventDispatcher, onMount } from "svelte"
 	import { v4 } from "uuid"
 	import * as clipboard from "@tauri-apps/api/clipboard"
 	import json from "$lib/json"
@@ -94,11 +94,12 @@
 				themes: {
 					name: "default",
 					dots: true,
 					icons: true
 				},
-				check_callback: true
+				check_callback: true,
+				force_text: true
 			},
 			search: {
 				fuzzy: true,
 				show_only_matches: true,
 				close_opened_onclear: false,
@@ -749,22 +750,22 @@
 					entityData.factory == "[modules:/zentity.class].pc_entitytype" && reverseReferences[entityID].some((a) => a.type == "parent")
 						? "far fa-folder"
 						: icons.find((a) => entityData.factory.includes(a[0]))
 						? icons.find((a) => entityData.factory.includes(a[0]))![1]
 						: "far fa-file",
-				text: `${entityData.name} (ref ${entityID})`,
+				text: `${sanitise(entityData.name)} (ref ${entityID})`,
 				folder: entityData.factory == "[modules:/zentity.class].pc_entitytype" && reverseReferences[entityID].some((a) => a.type == "parent") // for sorting and stuff
 			})
 		}
 
 		let index = 0
 		for (let entry of entity.comments) {
 			tree.settings!.core.data.push({
 				id: "comment-" + index,
 				parent: getReferencedLocalEntity(entry.parent) || "#",
 				icon: "far fa-sticky-note",
-				text: entry.name + " (comment)",
+				text: sanitise(entry.name) + " (comment)",
 				folder: false // for sorting and stuff
 			})
 
 			index++
 		}
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 9f1669b..6b21c6e 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -629,5 +629,12 @@ export function changeEntityHashesToFriendly(entity: Entity, friendly: Record<st
 }
 
 export function changeEntityHashesFromFriendly(entity: Entity, friendly: Record<string, string>) {
 	changeEntityHashesToFriendly(entity, Object.fromEntries(Object.entries(friendly).map((a) => [a[1], a[0]])))
 }
+
+export function sanitise(a: string) {
+	const sanitiser = new DOMParser().parseFromString(`<div></div>`, "text/html")
+	sanitiser.querySelector("div")!.innerText = a
+
+	return sanitiser.querySelector("div")!.innerHTML
+}
\ No newline at end of file

Vulnerable code: 	import { getReferencedLocalEntity } from "$lib/utils"
				check_callback: true
				text: `${entityData.name} (ref ${entityID})`,
				text: entry.name + " (comment)",
	import { changeReferenceToLocalEntity, genRandHex, getReferencedEntities, getReferencedExternalEntities, getReferencedLocalEntity, normaliseToHash, traverseEntityTree } from "$lib/utils"
				check_callback: true
				text: `${entityData.name} (ref ${entityID})`,
				text: entry.name + " (comment)",
Fixed code: 	import { getReferencedLocalEntity, sanitise } from "$lib/utils"
				check_callback: true,
				force_text: true
				text: `${sanitise(entityData.name)} (ref ${entityID})`,
				text: sanitise(entry.name) + " (comment)",
	import { changeReferenceToLocalEntity, genRandHex, getReferencedEntities, getReferencedExternalEntities, getReferencedLocalEntity, normaliseToHash, sanitise, traverseEntityTree } from "$lib/utils"
				check_callback: true,
				force_text: true
				text: `${sanitise(entityData.name)} (ref ${entityID})`,
				text: sanitise(entry.name) + " (comment)",

export function sanitise(a: string) {
	const sanitiser = new DOMParser().parseFromString(`<div></div>`, "text/html")
	sanitiser.querySelector("div")!.innerText = a

	return sanitiser.querySelector("div")!.innerHTML
}