- Electron is a technology that allows developers to use JavaScript to create desktop application.
- It achieves this by running a chromium instance for each window. Very wasteful imo
- This writeup is mostly content from the Electron Official Tutorial, but I added an intentionally vulnerable Electron app that allows XSS to turn into RCE as the last example.
- In Electron, “app” and “BrowserWindow” are two important concepts.
- BrowserWindow is an instance of Chromium that is running. Whenever we open a window, a new BrowserWindow is created. Users can directly call functions in the BrowserWindow.
- App is kind like engine that manages the “backend” stuff. Users are not able to directly call functions in the app.
Simple Electron App
- Initialize NPM dependencies
| |
- Add the following to scripts field
| |
index.htmlis the HTML code that will be rendered
| |
- In the
main.jsfile.
| |
readyevent documentation
- To enable debugging, we can use this vscode config
| |
- To start the application,
| |
Output:

Exposing Inter-Process Functions to Client Side
preload.jsallows us to expose certain functions into client-side processes.- A BrowserWindow’s preload script runs in a context that has access to both the HTML DOM and a limited subset of Node.js and Electron APIs.
- To add features to your renderer that require privileged access, you can define global objects through the contextBridge API.
- Use
contextBridgeinpreload.js
| |
- To tell browserWindow to preload a user-defined script,
| |
- Write HTML code that invokes the JavaScript function
id
| |
Exposing Functions that Calls Other Libraries to Client Side
- First, we need to establish a 2-way communication between the browser window and app in
preload.jswithipcRenderer
| |
- This means that we can access
window.electronAPI.pwnin the client-side JavaScript window.electronAPI.pwnwill throwcommand:execevent tomain.jsipcRenderer.invoke('command:exec', cmd)means that we can call this event viawindow.electronAPI.pwn(cmd)
- We must register a listener for
command:execinmain.js
| |
ipcMain.handle('command:exec', execCommand)registers the event and specifies which function will handle the event- The function will always accept at least one parameter. The first is
event, which passes info about the event. The second parameter onwards are the parameters sent by the client.
- To achieve remote code execution, open Developer console and enter
| |
XSS -> RCE in Electron
- First, we identify that there is a command injection vulnerability in the code in
execCommand
| |
- Notice
ipcMain.handle('command:exec', execCommand);. This means that when the client-side throws a command:exec event, it will be handled by execCommand function - Let’s check the
preload.jswhich is injected into each new client process
| |
- This means that there exists a API in the client side called
window.pwn.exec exec: (cmd) =>means that this command accepts 1 parameteripcRenderer.invoke('command:exec', cmd)means that this command throwscommand:execevent- So, we can trigger the command injection vulnerability with
window.pwn.exec(cmd)
- First, we confirm that we can achieve XSS
It is due a sink here
| |
- We can get a reverse shell via XSS Payload
| |
Output:
| |
References
- This blog post is inspired by Reunion CTF 2026 - ClientSide. I wanted to solve the challenge but couldn’t get it to work locally. Special thanks to the creator for this interesting challenge and solutions.
- Main references: