vm2 3.11.6では、特定の組み込み機能が許可されている場合、ホストの`crypto`モジュールが`NodeVM`に公開されます。このモジュールは読み取り専用のインターフェースを通じて提供されますが、その関数は依然としてホストプロセスの権限で実行されます。`crypto.setEngine()`はファイルシステムのパスを受け取り、OpenSSLに参照されたネイティブライブラリを動的にロードするように指示します。

悪意のあるプラグインパッケージにネイティブライブラリが含まれている場合、攻撃者はサンドボックス化されたJavaScriptから`crypto.setEngine()`を呼び出すことで、そのライブラリをホストプロセスにロードすることができます。ネイティブライブラリのコンストラクタは、OpenSSLがファイルが有効なエンジンであるかどうかを検証する前に実行されます。その結果、期待される`ERR_CRYPTO_ENGINE_UNKNOWN`エラーが発生するのは、任意のネイティブコードがすでに実行された後です。

この脆弱性を悪用するには、`crypto`組み込み機能のみが必要です。`fs`、`process`、`module`、`child_process`、`worker_threads`、`vm`、`inspector`、制限のない組み込み機能、またはvm2のネストは必要ありません。

### 詳細

脆弱な箇所は、汎用的な組み込み機能ローダーです。特にラップされていない、または危険と分類された組み込み機能は、ホストの環境にインポートされ、再帰的な読み取り専用プロキシを通じて公開されます。

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

読み取り専用により、サンドボックス化されたコードがモジュールオブジェクトにプロパティを割り当てることはできません。ただし、呼び出し可能なエクスポートの権限は低下しません。呼び出しは、ブリッジの値がホストの値に変換された状態で、元のホスト関数に転送されます。

`crypto`モジュールには、以下の呼び出し可能なエクスポートが含まれています。

```js
crypto.setEngine(enginePath)
```

`enginePath`が動的ライブラリを指している場合、OpenSSLはそのファイルを現在のプロセスにロードします。オペレーティングシステムの動的ローダーは、ライブラリのロード中にライブラリのコンストラクタを実行します。エンジンシンボルの検証は、その後に実行されます。ファイルが機能的な暗号エンジンとして動作しなくても、そのコンストラクタは実行されます。

結果として得られる攻撃の流れは以下のようになります。

攻撃者が、ネイティブライブラリを含む信頼できないプラグインパッケージを提供する。
-> ホストは、`crypto`のみが許可されたNodeVMでプラグインを実行する。
-> プラグインは`crypto.setEngine(pathToBundledLibrary)`を呼び出す。
-> `vm2`は、この呼び出しをホストの`crypto`モジュールに転送する。
-> OpenSSLは、オペレーティングシステムのローダーにライブラリのロードを要求する。
-> ライブラリのコンストラクタが、ホストプロセス内でネイティブコードを実行する。
-> OpenSSLは、その後、ファイルを拒否する可能性があるが、ホストの侵害はすでに発生している。

これは、単にJavaScriptのファイルシステムアクセスを許可するのとは異なる。プラグインシステムでは、JavaScriptを実行する前に、通常、信頼できないパッケージをディスクに配置する必要がある。したがって、サンドボックスが`fs`やすべてのプロセス実行機能を拒否している場合でも、ネイティブファイルはすでにそのパッケージ内に存在している可能性がある。`crypto`をハッシュまたは署名検証のために許可すると、その不活性なパッケージファイルが、予期せずネイティブコード実行の手段となる。

### PoC (概念実証)

添付のPoCは、ローカルで無害である。そのネイティブライブラリのコンストラクタは、マーカーファイルを作成するだけで、プロセスを起動したり、ネットワーク接続を開いたり、他のファイルを変更したりすることはない。

1. OpenSSLエンジンサポート、Cコンパイラ、およびnpmをインストールしたNode.jsをインストールする。
2. 添付の`poc`ディレクトリで、影響を受けるパッケージを正確にインストールする。

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

3. マーカーライブラリをビルドする。

macOSの場合:

```bash
cc -dynamiclib -O2 -o probe-engine.dylib engine_probe.c
node poc.js ./probe-engine.dylib
```

Linuxの場合:

```bash
cc -shared -fPIC -O2 -o probe-engine.so engine_probe.c
node poc.js ./probe-engine.so
```

4. 脆弱な結果は、以下のすべてを含む。

- サンドボックスの設定では、許可された組み込み機能として`crypto`のみがリストされている。
- `crypto.setEngine()`が`ERR_CRYPTO_ENGINE_UNKNOWN`を報告するか、または何も返さない。
- `vm2-setengine-native-marker.txt`が存在する。
- マーカーファイルには`VM2_SETENGINE_NATIVE_CODE_EXECUTED`が含まれている。

例外は、必ずしもネガティブな結果を意味するものではありません。このエラーは、エンジンによる検証が失敗する前に、ネイティブコンストラクタが実行されたことを示しています。

### 影響

これは、サンドボックスを突破し、任意のネイティブコードを実行する脆弱性です。このコードは、Node.jsホストプロセスのオペレーティングシステムの権限とIDで実行され、`vm2`言語およびモジュールのすべての制限を回避します。

攻撃者は、マーカーのみを持つコンストラクタを、以下の機能を持つネイティブコードに置き換えることができます。

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

実際に影響を受ける可能性のあるワークフローは、攻撃者が提供したパッケージの内容をディスクに保存し、`NodeVM`内でパッケージのJavaScriptを実行し、同時に`crypto`モジュールを許可する、プラグイン、自動化ツール、ノートブック、またはマルチテナントコードランナーです。攻撃者は、サンドボックスを突破するために、ファイル書き込みまたはコマンド実行モジュールを公開する必要はありません。

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