TL;DR

Links to third-party websites should be properly validated and checked before opening in the Electron JS applications. If the protocol of the link is not whitelist to http:// or https:// only, an Electron application becomes vulnerable to 1-click RCE attacks. This kind of attack exploits the Electron model and user’s navigation mechanism which redirects a user from the Electron app to the browser.

Introduction

In my previous blog post, I described the architecture and design for my theoretical misconfigured Electron application. I would suggest to read the introduction part to fully understand the context of the application described in this blog post.

This is a second blog post related to the importance of user limiting in an Electron native application. By opening not app specific links in the native application, It’s fairly common for the Electron framework to open them with unsafe function that redirects a user to the browser. This mechanism should be limited on the app business logic level to decrease the risk of being exploited.

RCE at the second attempt

If the Electron desktop application is deployed with proper nodeIntegration, contextIsolation settings; it simply means that client-side RCE by targeting preload scripts or Electron native code from the main process can not be achieved.

Currently there are no direct exploits or bypasses of these controls meaning if they are set up in a strict fashion then client-side RCE cannot be achieve.

However, during app navigation review, it is important to determine what links desktop app will open in the new desktop app windows and what will open in the browser. Let’s consider the case when all the links outside the main domain should open in a separate web browser.

Review of the app navigation [shell.openExternal]

Each time a user clicks the link or opens a new window, the following event listeners are invoked:

webContents.on("new-window", function (event, url, disposition, options) {}
webContents.on("will-navigate", function (event, url) {}

The desktop application overrides these listeners to implement the desktop application’s own business logic. During the creation of new windows, the application checks whether the navigated link should be opened in a desktop application’s window or tab, or whether it should be opened in the web browser. In our example the verification is implemented with the function openInternally, if it returns false, the application will assume that the link should be opened in the web browser using the shell.openExternal function.

The openInternally function has a predefined list of domains whose contents should be rendered in the application’s desktop (In this example only application domain).

Here is a simplified pseudocode:

function openInternally (url) {
		parsed_url = new URL(url)
		if (parsed_url.protocol === "https:"){
				if (new RegExp("application-domain.com", "i").test(parsed_url.host)) {
						return true;
				}
    }
    return false;
}
webContents.on("new-window", function (event, url, disposition, options) {
			if (openInternally(url)) {
	            if ( ... [check of the tab supporting, nothing important] ... ) {
	                var newTab = createNewTab({ ... [settings] ... });
	                preventDefault(newTab);
	            } else {
	                var newWindow = createNewWindow({ ... [settings] ... });
	                preventDefault(newWindow);
	            }
			} else {
			      preventDefault();
			      electron.shell.openExternal(url);
			}
}