vm2: デフォルトのVMは、ホストのTypedArrayおよびArrayBufferの内部オブジェクトを、ホストプロトタイプ汚染の修正後も変更できる可能性があります。
## 概要
vm2の現在のホスト内部オブジェクトの保護は不完全です。`GHSA-vwrp-x96c-mhwq`の修正により、サンドボックスからの書き込みが、`Object.prototype`、`Array.prototype`、`Function.prototype`などの従来のホスト内部オブジェクトへの影響は防がれますが、現在のバージョンでは、デフォルトの`VM`内のサンドボックスコードが、ホストの`Uint8Array.prototype`、`%TypedArray%.prototype`、および`ArrayBuffer.prototype`にアクセスし、変更できる状態です。
`VM.run()`が完了した後、通常のホストのTypedArrayおよびArrayBufferオブジェクトは、サンドボックスによってインストールされた攻撃者が制御できるプロパティとメソッドを認識します。
## 技術的な詳細
既存の対策は、`lib/bridge.js`にある`protectedHostObjects`に依存しています。このセットは、固定された一連のグローバルオブジェクトから構築される`otherGlobalPrototypes`から作成されます。
```js
const globalsList = [
'Number', 'String', 'Boolean', 'Date', 'RegExp', 'Map', 'WeakMap',
'Set', 'WeakSet', 'Promise', 'Function'
];
```
このリストには、TypedArrayおよびArrayBufferの内部オブジェクトは含まれていません。サンドボックスコードは、以前の公開された注意喚起で示された、ホストプロトタイプを走査する基本的な手法を依然として使用できます。
```js
const lookupGetter = ({}).__lookupGetter__;
const apply = Buffer.apply;
const protoGetter = apply.apply(lookupGetter, [Buffer, ['__proto__']]);
const hostBuffer = Buffer.from([1]);
const hostBufferPrototype = protoGetter.call(hostBuffer);
const hostUint8ArrayPrototype = protoGetter.call(hostBufferPrototype);
const hostTypedArrayPrototype = protoGetter.call(hostUint8ArrayPrototype);
const hostArrayBufferPrototype = protoGetter.call(hostBuffer.buffer);
```
これらのオブジェクトは、サンドボックス内に存在しません。サンドボックス内では、`hostUint8ArrayPrototype === Uint8Array.prototype`、`hostTypedArrayPrototype === Object.getPrototypeOf(Uint8Array.prototype)`、および `hostArrayBufferPrototype === ArrayBuffer.prototype` はいずれも `false` となります。オブジェクトが `protectedHostObjects` に含まれていないため、ブリッジの `defineProperty` による書き込みは、実際のホストオブジェクトに転送されます。
これは、以前のホスト組み込みプロトタイプ汚染の修正と同様の境界ですが、一部の組み込みオブジェクトが欠落しています。ブリッジはすでに正しい検証機構を持っていますが、保護対象の範囲が狭すぎます。
## 実証コード
実証コード: `poc/pov-host-typedarray-arraybuffer-prototype-pollution.js`
現在のヘッドでの出力:
```json
{
"status": "completed",
"vulnerable": true,
"node": "v25.8.0",
"v8": "14.1.146.11-node.20",
"control": {
"defineResult": true,
"sandboxReadsBack": "sandbox-only",
"hostControlAfter": null
},
"exploit": {
"hostUint8IsSandboxUint8": false,
"hostTypedArrayIsSandboxTypedArray": false,
"hostArrayBufferIsSandboxArrayBuffer": false,
"defineUint8": true,
"defineTypedArray": true,
"defineArrayBuffer": true,
"defineMethod": true
},
"hostEffect": {
"uint8Marker": "polluted-host-uint8array-prototype",
"typedArrayMarker": "polluted-host-typedarray-prototype",
"arrayBufferMarker": "polluted-host-arraybuffer-prototype",
"methodReturn": "sandbox-method-reached-host-uint8array"
}
}
```
この結果は、サンドボックス内の `Uint8Array.prototype` への書き込みがサンドボックス内に留まることを示しています。エクスプロイトの経路は、ブリッジを介してホストプロトタイプに到達し、`VM.run()` が終了した後、ホストによって作成されたオブジェクトがサンドボックスにインストールされたプロパティを認識する原因となります。
## 影響
これは、サンドボックスの境界違反およびホストの組み込みプロトタイプの汚染です。 デフォルトの `vm2` の `VM` でJavaScriptを実行できる攻撃者は、`NodeVM`、`require`、ワイルドカードの組み込み関数、`nesting: true`、またはホストが提供する型付き配列オブジェクトを使用せずに、共有ホストの型付き配列および `ArrayBuffer` の動作を書き換えることができます。
提供されたPoC(概念実証)は、ホストのプロトタイプにマーカーとメソッドをインストールすることで、整合性と可用性に与える影響を示しています。
- `Uint8Array.prototype`
- `%TypedArray%.prototype`:これは、共有の型付き配列プロトタイプチェーンを通じて、すべての型付き配列ファミリーに影響を与えます。
- `ArrayBuffer.prototype`
このPoCは、ホストコマンドの直接実行や、直接的な機密性への影響を主張するものではありません。 これはローカルでのみ実行され、終了時に変更された記述子を復元します。
## 推奨される修正
保護対象のホストオブジェクトのリストと、型付き配列およびバイナリデータの組み込み関数との間のID/プロトタイプのマッピングを拡張します。 少なくとも、以下のものを対象とします。
- `%TypedArray%.prototype`
- `ArrayBuffer.prototype`
- `SharedArrayBuffer.prototype`(存在する場合)
- `DataView.prototype`
- 実行時に存在するすべての具体的な型付き配列プロトタイプ。具体的には、`Uint8Array.prototype`、`Uint8ClampedArray.prototype`、`Int8Array.prototype`、`Uint16Array.prototype`、`Int16Array.prototype`、`Uint32Array.prototype`、`Int32Array.prototype`、`Float16Array.prototype`(存在する場合)、`Float32Array.prototype`、`Float64Array.prototype`、`BigInt64Array.prototype`、および `BigUint64Array.prototype`。
一時的な修正として、この組み込み関数ファミリーを `thisGlobalPrototypes` に追加したものが、同じPoCの `Reflect.defineProperty()` 呼び出しを `VMError: Operation not allowed on contextified object` でエラーにする原因となり、ホストのマーカーはインストールされませんでした。
この修正は、既存のホストの組み込み関数保護で使用されている、`set`、`defineProperty`、`deleteProperty`、および `preventExtensions` と同じホストの変更を検知するメカニズムを対象とする必要があります。 `Buffer` を特別に扱う必要はありません。`Buffer` は、省略されたホストプロトタイプにアクセスするための単なる方法の一つです。
## 影響を受けるパッケージ/バージョン
現在の最新バージョン `7a1f5100b96f48d34e0fe104ab37c0acc5944f92` / `v3.11.5` で、Node `v25.8.0` / V8 `14.1.146.11-node.20` 環境で確認されています。
以前の修正プログラム適用後も影響が確認されました: `npm:vm2 >= 3.11.0, <= 3.11.5`.
ローカル環境でのテストでも、`v3.10.0` から `v3.10.5` のバージョンで同様の問題が再現されますが、この範囲は既に公開されている `GHSA-vwrp-x96c-mhwq` の範囲と重なります。`v3.9.0` から `v3.9.2` のバージョンでは、同じローカルテスト環境でホスト環境に影響を与える問題は発生しませんでした。
## なぜこれが意図された動作ではないのか
vm2 のドキュメントでは、`VM` は `require` を使用しない信頼できないコードのためのサンドボックスであり、デフォルトでは JavaScript の組み込み機能と Node.js の `Buffer` のみが利用可能とされています。既知のエスケープ方法では、この問題の説明になりません。
- `require.builtin: ['*']` は `NodeVM` の設定であり、この検証ではデフォルトの `VM` を使用しています。
- `nesting: true` は有効になっていません。
- README に記載されているタイムアウトに関する注意は、サンドボックスから返されたオブジェクトに対してホストコードが動作する場合に該当しますが、この検証ではホスト環境の内部オブジェクトが変更され、その結果、ホスト環境が作成した通常の TypedArray や ArrayBuffer に影響を与えます。
このプロジェクト自身の攻撃に関する記述では、ホスト環境の内部オブジェクトのプロトタイプはサンドボックスからの書き込みから保護されるべきであり、意図的に公開されたホストオブジェクトは変更可能であるとされています。TypedArray と ArrayBuffer のプロトタイプは、埋め込みアプリケーションオブジェクトではなく、ホスト環境の内部オブジェクトです。
`Buffer` の利用可能性は、この検証がホスト環境のプロトタイプチェーンに到達する方法を説明しますが、関連性のないホスト環境の内部オブジェクトの変更を許可するものではありません。ホスト環境への影響は、`VM.run()` が完了した後、ホスト環境が作成した新しい TypedArray と ArrayBuffer で観察されます。ホスト環境は、サンドボックスから返されたオブジェクトを呼び出しているわけではありません。
影響を受けるパッケージ:
- npm (JavaScript) vm2: 影響あり >= 3.11.0, <= 3.11.7、3.11.8 で修正済み。