0-click RCE in Electron Applications

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

April 20, 2022 · PAVEL SHABARKIN, PENETRATION TESTER

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.

// The `require` function reassignment / redeclaration
var ElectronRequire = require // The actual problem lies in this line of code

const {contextBridge} = ElectronRequire('electron')
const {readFile, writeFile} = ElectronRequire('fs/promises')

contextBridge.exposeInMainWorld('settingsAPI', {
    getSettings: () => readFile('path/to/user-settings.json').then(JSON.parse),
    saveSettings: (value) => writeFile('path/to/user-settings.json', JSON.stringify(value)),
})

The preload.js file are special JS scripts that will be executed before every web page load. It is declared individually for each window:

new BrowserWindow({
    webPreferences: {
        preload: 'preload.js'
    }
})

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.

References

  1. https://mksben.l0.cm/2020/10/discord-desktop-rce.html?m=1
  2. https://www.electronjs.org/docs/latest/tutorial/security#14-do-not-use-openexternal-with-untrusted-content

News & Updates...

We are happy to share our methodology and security guide on how to do security reviews for Ruby on Rails applications through source code. In the article you will get an idea about the architecture and design of Ruby on Rails, present security checklist to increase the coverage for penetration testing assessments, and review how to find and exploit most of the OWASP 10 vulnerabilities.

Join us in exploring Meteor JS vulnerabilities.

XSS can be particularly devastating to Electron apps, and can result in RCE and phishing that might not be viable in a browser. Electron has features to mitigate these problems, so applications should turn them on. Even XSS that would be low-impact in the browser can result in highly effective phishing if the application’s URL allowlist is improperly designed. Attacks exploit the Electron model and the application-like presentation of Electron to gain the user’s confidence.