Angular Server-Side Rendering (SSR): 数値のURLパラメータによるサービス拒否

Node.js (V8)でServer-Side Rendering (SSR)が有効になっている場合、`@angular/router`にサービス拒否 (DoS) の脆弱性が発見されました。

`@angular/router`が受信リクエストのURLを解析する際、パスのセグメント、マトリックスパラメータ、および子アウトレットを、プレーンなJavaScriptオブジェクト (`Record<string, string>`) に変換します。マトリックスパラメータの名前やアウトレットの名前が数値の文字列 (例: `/a;990;2522`) の場合、V8 JavaScriptエンジンは、それらを名前付きのプロパティではなく、配列のインデックスとして扱います。

V8の内部プロパティストレージの仕組みでは、最初に空のオブジェクトに数値のキーを設定すると、V8は、最大インデックスに合わせてサイズが決定された、連続した配列のバックストア (`HOLEY_ELEMENTS`) を割り当てます。具体的には、連続した、または比較的大きな数値のキー (例: `990`の後に`2522`) を割り当てると、V8は、単一の11バイトのセグメントに対して、約2,522個のポインタ (約20 KBから25 KBのヒープ) を持つ、連続したバックストアを割り当てます。

URLパス内の各セグメントが独自の独立した`parameters`オブジェクトを割り当てるため、攻撃者は、繰り返し数値のマトリックスパラメータを含むURLを作成することで、非対称なメモリ増幅効果を約**~350倍**にすることができます。

### 影響

攻撃が成功すると、認証されていないリモート攻撃者が、比較的少ないリクエスト数でNode.jsの古いヒープ領域を枯渇させ、回復不可能な`JavaScript heap out of memory`エラーを引き起こし、SSRワーカーを停止させ、サービス拒否を引き起こす可能性があります。

* **高い増幅:** 単一の11バイトのセグメント (`/a;990;2522`) は、V8のヒープメモリを約20KB~25KB消費します。
* **低い同時実行性:**
* 8KBの要求パス(約740セグメント、デフォルトのNginx 8KBバッファ制限内)の場合、わずか**12~22件の同時リクエスト**で、256 MiB~512 MiBのNode.js SSRワーカーがクラッシュします。
* より小さい1KB~2KBの要求パス(約90~180セグメント)の場合、約50~100件の同時リクエストでも、同様にヒープメモリが枯渇します。
* **クライアントサイドのSPAには影響なし:** クライアントサイドのみで動作するAngularアプリケーション(SSRを使用しないSingle Page Application)は、**影響を受けません**。これは、ローカルブラウザのメモリ使用量がセキュリティ上の制限を超えないためです。

### 攻撃の前提条件と脆弱な設定

アプリケーションが影響を受けるのは、以下の**すべての**条件を満たす場合のみです。

* **SSRが有効:** アプリケーションが、Node.js / V8によって提供されるサーバーサイドレンダリング環境で動作している。
* **直接的なルーティング解析:** ユーザーが制御するリクエストURLが、SSR中に`@angular/router`によって解析される。
* **リバースプロキシによるセミコロン/セグメントのフィルタリングなし:** 上流のリバースプロキシ(Nginx、Cloudflare、ALBなど)が、セミコロン(`;」)を含むURLや複数のパスセグメントを含むURLを、フィルタリングや拒否なしに転送する。

#### 攻撃ペイロードの例

攻撃者は、繰り返し数値の行列パラメータを含む同時HTTPリクエストを送信します。

```http
GET /a;990;2522/a;990;2522/a;990;2522/... HTTP/1.1
Host: example.com
```

パスが2KB未満の場合でも、SSR中に重なるリクエストがV8のヒープメモリを急速に消費し、プロセスがクラッシュします。

### 修正

この問題は、`@angular/router`を更新し、数値のURLから派生したキー(index >= 32)に対して、V8の辞書要素のストレージ(`setUrlDerivedKey`)を適用することで解決されます。これにより、V8が過剰な連続した配列のメモリ領域を割り当てるのを防ぎつつ、ルーティングの一致、パラメータ値、およびコンポーネントの入力バインディングを維持します。

* `22.2.0`
* `21.2.24`
* `20.3.32`

### 対策と回避策

もし、すぐに修正版にアップデートできない場合は、エッジまたはリバースプロキシで以下のいずれかの対策を適用してください。

1. **リバースプロキシでMatrixパラメータをブロックまたはサニタイズする:**
リバースプロキシ(例:Nginx、Cloudflare、またはAWS WAF)を設定し、Angular SSRサービスにリクエストを転送する前に、リクエストパスに含まれるセミコロン(`;`)を拒否または削除します。
```nginx
# Nginxの例:Matrixパラメータを含むリクエストを拒否する
if ($uri ~* ";") {
return 400;
}
```
2. **パスセグメントの厳格な制限を適用する:**
パスの深さが過度なリクエスト(例:20~30セグメント以上)を拒否します。
3. **Node.jsのOld Spaceのサイズを増やす:**
`--max-old-space-size`の値を大きくします(例:2048または4096MB)。これにより、メモリを使い切るまでに必要な同時実行数の閾値を上げることができますが、継続的なトラフィック下では脆弱性を完全に解消するわけではありません。

影響を受けるパッケージ:
- npm (JavaScript) @angular/router: 影響を受けるバージョン >= 22.0.0、< 22.2.0、22.2.0で修正済み
- npm (JavaScript) @angular/router: 影響を受けるバージョン >= 21.0.0、< 21.2.24、21.2.24で修正済み
- npm (JavaScript) @angular/router: 影響を受けるバージョン >= 20.0.0、< 20.3.32、20.3.32で修正済み
- npm (JavaScript) @angular/router: 影響を受けるバージョン <= 19.2.25、修正なし