probe-image-size: SVGパーサーにおけるクアドラティック時間のDoS攻撃
## 概要
`probe-image-size`は、SVGヘッダーを検索用の正規表現 `/<[-_.:a-zA-Z0-9][^>]*>/` でスキャンします。`<` 文字が多く含まれているが `>` 文字が含まれていない入力の場合、エンジンは `[^>]*` のスキャンを、`<` が出現するすべての位置で再開し、毎回入力の最後まで実行するため、クアドラティックな時間複雑度が発生します。
同期パーサーとストリーミングパーサーの両方に影響があります。
## 影響
SVGパーサーに到達するすべてのエントリポイントに影響があります。具体的には、`probe.sync()`、`probe(stream)`、および `probe(url)` です。URL形式が最も脆弱です。入力はリモートホストから取得されるため、攻撃者はリンクのみを提供すれば攻撃が可能です。
細工されたバッファを処理すると、Node.jsのイベントループが100%のCPU使用率で動作し続けます。アップロード検証、画像プロキシ、またはリンク展開サービスなどの本番環境では、少数の同時リクエストでサービスを停止させることができます。
## 根本原因分析
2つの独立した問題があります。
1. **同期パスにおける入力サイズ制限の欠如。** `lib/parse_sync/svg.js` は、バッファ全体を文字列にコピーし、それに対してマッチングを行っていました。サイズ制限が全くないため、コストは攻撃者が提供するバッファのサイズに比例して増加します。
2. **ストリームパスにおける繰り返しスキャン。** `lib/parse_stream/svg.js` では、累積データを64 KBに制限していますが、*すべての*チャンクに対して、累積された文字列全体に対して `parseSvg(str)` を呼び出しており、`O(chunks × N²)` という時間複雑度になります。この制限は効果がありません。入力がより多くのチャンクに分割されるほど、クアドラティックなスキャンが繰り返される回数が増えます。
チャンクサイズは送信元に影響されます。`highWaterMark` (16 KB) はバッファリングの閾値であり、下限ではありません。ソケットからの読み取りは、実際に受信したデータ量を返します。サーバーが1バイトずつ書き込む場合、1バイトのチャンクが生成されます。これは、デフォルトオプションで設定された実際の `needle` パイプラインで確認されています。
元の報告書では、(1) の問題のみが特定され、64 KBの制限がストリーミングパスを軽減すると述べられていましたが、これは誤りです。
## 実証 (PoC)
同期:
```js
const probe = require('probe-image-size');
// 約200KBの文字列 '<a' — '<' は含まれるが、'<' は含まれない
probe.sync(Buffer.from('<a'.repeat(100000), 'latin1'));
```
ストリーミング:同じペイロードをチャンクに分割したもの。同期処理よりも1バイトあたりに時間がかかる:
```js
const { Readable } = require('stream');
const probe = require('probe-image-size');
const payload = Buffer.from('<a'.repeat(32768), 'latin1');
const chunks = [];
for (let i = 0; i < payload.length; i += 4096) {
chunks.push(payload.subarray(i, i + 4096));
}
await probe(Readable.from(chunks));
```
開発者のマシンでの測定結果:
| パス | 入力 | 時間 |
| --- | --- | --- |
| `probe.sync()` | 25 KB | 0.9 秒 |
| `probe.sync()` | 50 KB | 5.5 秒 |
| `probe.sync()` | 100 KB | 18 秒 |
| `probe.sync()` | 200 KB | 54 秒 |
| `probe(stream)` | 64 KB, 1 チャンク | 1.6 秒 |
| `probe(stream)` | 64 KB, 4 チャンク | 2.9 秒 |
| `probe(stream)` | 64 KB, 16 チャンク | 9.6 秒 |
影響を受けるパッケージ:
- npm (JavaScript) probe-image-size: 影響を受けるバージョンは 7.3.0 以下、7.4.0 で修正済み。