TL;DR

If the Electron application is not configured with appropriate features any XSS vulnerability can be disruptive, and result in 0-click client-side RCE attacks. This kind of attack exploits the Electron model and bypasses user’s sandbox mechanism.

Introduction

General application architecture review

For the presented example lets assume that we’ve discovered a stored XSS vulnerability in the mail box functionality (ie XSS can be achieved by sending an email to a user) in email wrapper application and that the current application is built specifically as a Desktop application.

If reviewing the source code grep for and find Electron native functions such as: (Desktop.shell.openExternal , Desktop.shell.realPath , etc ...) This means that the architecture of Electron native application (desktop app) is poorly designed and contextIsolation and nodeIntegration settings are potentially disabled. In case where the Electron app had been configured securely, the Electron native code loaded from remote origin would never execute. Those settings can also be checked by decompiling the Desktop application’s .asar file, and reviewing the webPreferences settings for all renderers in the desktop app.

Is it easy to achieve RCE in Electron?

Planning an attack vector

While reviewing the code of our example decompiled desktop application (.asar file), it is not a surprise to find that all the application window and tab renderers are set to contextIsolation:false. Any architectural solution that allows Electron native functions to be loaded from remote origins with application’s client-side JavaScript code introduces significant security and maintainability issues.

Based on this, we could plan an attack vector to achieve client-side RCE in two possible ways:

  1. Attack the preload.js script by abusing the already existing code of the Desktop application
  2. Attack the native JavaScript functions by overwriting their logic through global JavaScript __proto__ object

Theory

The contextIsolation is an Electron feature that allows developers to run code in preload scripts and in Electron APIs in a dedicated JavaScript context. In practice, this means that global objects such as Array.prototype.push or JSON.parse cannot be modified by scripts running in the renderer process.

It introduces separated contexts between Electron's native JavaScript code and the web application’s JavaScript code, so that execution of the application's JavaScript code does not affect the native code.

If contextIsolation is disabled, the web application’s JavaScript code may affect execution of Electron's native JavaScript code on the renderer and preload scripts. This behavior is dangerous because Electron allows the web application’s JavaScript code to use the Node.js features regardless of the nodeIntegration option. By interfering with them from the function overridden in the web page, RCE can be achieved even if nodeIntegration is set to false.

Even if nodeIntegration: false is used, to truly enforce strong isolation and prevent the use of Node primitives contextIsolation must also be used.

Back to the attack

By reviewing the preload.js scripts it’s fairly common to identify functionality that could be abused in the attack, for example require function can be reassigned and redeclared with a new namespace to avoid confusions with the require functions from the main renderer.