Axios: `fromDataURI` データURIのURLパーサーにおけるReDoS(リソース枯渇攻撃)により、Node.jsのイベントループが停止する(DoS攻撃)
## 概要
Node.js版Axiosは、`lib/helpers/fromDataURI.js`で`data:`形式のURLを解析します。現在のRFC-2397に準拠したパーサーは、正規表現を使用しており、その正規表現のメディアタイプグループでは、`type/subtype`の区切り文字の両側に`/`が含まれることを許可しています。多くのスラッシュが含まれており、カンマが含まれていない不正な`data:`形式のURLがあると、JavaScriptの正規表現エンジンは、失敗するまで、区切り文字の配置について多くの可能性を試行します。
アプリケーションが、信頼できないURL文字列をAxiosに渡し、Axiosが解析する前に`data:`形式のURLを拒否または制限しない場合に、影響を受けます。
## 影響
攻撃者は、不正なURLを1つ解析するだけで、Node.jsのイベントループが大量の同期CPU時間を消費するようにすることができます。HTTPリクエストからURLを受け取り、`axios.get(url)`を呼び出すサーバーでは、解析が完了するまで、関連性のないリクエストやヘルスチェックがブロックされる可能性があります。
この問題は、可用性にのみ影響を与え、データは開示されず、リクエストも変更されません。
## 影響を受ける機能
影響を受けるもの:
- Node.jsのHTTPアダプターにおけるデータURLの処理。
- `axios.get()`またはそれに相当する呼び出しで、`config.url`に`data:`プロトコルが含まれている場合。
影響を受けないもの:
- ブラウザのfetch/XHRにおけるURLの処理。
- アプリケーションがAxiosを呼び出す前に`data:`形式のURLを拒否するNode.jsリクエスト。
- 以前のバージョン(`0.x`)のデータURLパーサーで、個別に脆弱性が確認されていない場合。
## 技術的な詳細
`lib/helpers/fromDataURI.js`には、以下のコードが含まれています。
```js
const DATA_URL_PATTERN = /^([^,;]+\/[^,;]+)?((?:;[^,;=]+=[^,;]+)*)(;base64)?,([\s\S]*)$/;
```
`[^,;]+`のグループには`/`が含まれているため、カンマがない長い文字列は、必要な`\/`の周りで、多くの方法で分割される可能性があります。このマッチングは、Axiosがリクエストのタイムアウト処理を適用する前に実行されるため、`timeout`設定は解析の停止を防ぐことはできません。
axiosのバージョン`1.18.1`において、小さなデータ量を送信した場合、1000個のスラッシュでは約2.4ms、3000個のスラッシュでは16.6ms、6000個のスラッシュでは71.5msという処理時間となり、これは提出された二次関数の増加傾向と一致しており、大きなデータ量を送信しない場合に確認できます。
## 攻撃の概念実証
制限されたヘルパーレベルでのデモンストレーション:
```js
import axios from 'axios';
await axios.get('data:' + '/'.repeat(6000));
```
このリクエストは解析後に失敗しますが、失敗は同期の正規表現処理によって遅延します。データ量が多いほど、この遅延は大幅に増加します。
## 対策
axiosに信頼できない入力を渡す前に、`data:` URLを拒否するか、URL取得エンドポイントに対して厳密な最大URL長を適用してください。`data:` URLのサポートが不要なアプリケーションは、明示的にそのプロトコルを拒否する必要があります。
<details>
<summary><h3">元の報告書</h3></summary>
# `fromDataURI`のデータ:URLパーサーによるReDoS(イベントループのフリーズによるDoS攻撃)
## 対象
- パッケージ: `axios` (Node.js HTTPアダプター)
- バージョン: 1.x (v1.xに正規表現が存在、現在のリリースライン)
- ファイル: `lib/helpers/fromDataURI.js`
- CWE-1333 (非効率な正規表現の複雑さ)
## 問題点
`fromDataURI`は、以下の正規表現を使用して`data:` URLを解析します。
```js
// lib/helpers/fromDataURI.js:9
const DATA_URL_PATTERN = /^([^,;]+\/[^,;]+)?((?:;[^,;=]+=[^,;]+)*)(;base64)?,([\s\S]*)$/;
```
メディアタイプトークン`[^,;]+`には`/`が含まれるため、複数のスラッシュを曖昧に解釈する可能性があります。多くのスラッシュで構成された`data:` URLは、エンジンに単一の`\/`区切り文字を配置するすべての方法を試させ、最終的に失敗します。これは二次関数のO(n²)のバックトラックを引き起こします。この処理はメインスレッドで同期的に実行されるため、Nodeのイベントループ全体が解析中にフリーズします。これは、NodeのHTTPアダプターの公開API(`axios.get(url)`)を通じて到達可能です。ブラウザのfetch/xhrアダプターは影響を受けません。なぜなら、それらは`fromDataURI`を呼び出さないからです。
設定された`timeout`は効果がありません。フリーズはネットワークタイマーが開始される前に、解析中に発生します。
## PoC (最小限の例)
```js
import axios from 'axios';
// 約256KBのデータ: ピリオドを含まないスラッシュのみで構成されたURL
await axios.get('data:' + '/'.repeat(262139)); // イベントループをブロックし、約6分後にエラーが発生
```
## 実験結果
シングルスレッドで、ユーザーが指定したURLを取得するサービス(リンクプレビューのようなもの)。`axios.get`を使用して、JSON形式のデータ `{ "url": "..." }` を送信し、`timeout: 1000` を設定。
スケーリングは、ほぼO(n²)の特性を示します(定数kは約5.6e-6 ms/byte²で、サイズに関わらず安定)。
| データURLのサイズ | イベントループの停止時間(1リクエストあたり) |
| --- | --- |
| 32 KB | 6.3秒 (エンドツーエンドで測定; サーバーは「event loopが6.30秒間ブロックされました」と記録) |
| 64 KB | 約24秒 |
| 128 KB | 約96秒 |
| 256 KB | 約385秒 (約6.4分) |
| 1 MB | 約100分 |
停止中は、サーバーは一切応答しません。`/healthz`のヘルスチェックが、停止期間全体でタイムアウトし、サーバー自身のイベントループ監視機能も、解析が完了するまでログを出力できません。プロキシを経由して実行した場合、プロキシが120秒のアップストリームタイムアウトに達し、処理を中断しますが、オリジンサーバーは、その時点でも1つのコアで100%のCPU使用率が継続します。クライアント/プロキシのタイムアウト設定では、この問題を軽減できません。
攻撃者は、URL文字列のみを制御でき、認証は不要です。約256KBのデータを約6分ごとに送信することで、サーバーを常に利用不能にすることができます(約0.7バイト/秒)。
## 影響
認証なしのリモートDoS攻撃。小さなリクエストが、Node.jsサービス全体を数分間停止させます。継続的にリクエストを送信すると、サービスは永久に停止状態になります。
CVSS 3.1: 7.5 (High) — `AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H`.
## 修正
RFC 2045で定義されているタイプ/サブタイプのトークンには、`/`は含まれません。`/`をこれらの2つの文字クラスから除外することで、曖昧さを解消し、バックトラックを回避できます。
```js
const DATA_URL_PATTERN = /^([^,;/]+\/[^,;/]+)?((?:;[^,;=]+=[^,;]+)*)(;base64)?,([\s\S]*)$/;
```
最悪の場合の解析時間は、約2600msから約0.002msに短縮されます。スラッシュを含む本文を持つ、すべての有効な`data:` URLは、同じように解析されます。
</details>
---
影響を受けるパッケージ:
- npm (JavaScript) axios: 影響を受けるバージョンは、1.16.1以上、1.20.0未満。1.20.0で修正済み。