0-click RCE in Electron Applications
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.
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:
- Attack the
preload.js
script by abusing the already existing code of the Desktop application - 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.
The preload.js
file are special JS scripts that will be executed before every web page load. It is declared individually for each window:
The preload.js
module is intended to create narrow, controlled interfaces through which the renderer.js
can interact with the Node.js API:
- In the
preload.js
file you create a global method. - And in the
renderer.js
file use it. All the methods created in preload file will have access to Node.js even if they are called in the renderer.
It simply means that ElectronRequire
function could be used to import any module we would like to work with, and to use Node.js features within the renderer. In this situation it is straight forward to craft a payload for the exploitation process. Using our theoretical found XSS vulnerability, It is possible to craft the following payload to leverage the 0-click client-side RCE attack against desktop users:
<img src=1 onerror='ElectronRequire("child_process").exec("uname -a",function(error,stdout,stderr){console.log(stdout);});'>
Summary
Developers can and do intentionally disable contextIsolation
setting. They can implement native Electron functions within normal business logic of client-side JS code, instead of overwriting and limiting Electron event listeners that define similar business logic (for example: new window, tab or file opening). In the presented scenario, an attacker could send emails to application users with the devastating XSS payload to compromise the user’s devices, if the email is opened.
Developers should always enable isolation (set contextIsolation
to true
, and nodeIntegration
to false
), and for this particular case refactor the rest of the functionality of the web application that uses native Electron code and is loaded to the renderer from the remote origin.
The complete guide on how to test Electron applications you may find here: