Axios: `shouldBypassProxy`におけるReDoS (O(N²)) の脆弱性。信頼できないリダイレクトの `Location` 経由で到達可能。

## 概要

Axiosの`shouldBypassProxy()`関数は、ホスト名を`hostname.replace(/\.+$/, '')`で正規化します。ホスト名が多数のドットの後にドット以外の文字が続く場合、アンカー付きの正規表現により、二次的なバックトラックが発生する可能性があります。Axiosは、リダイレクトされたリクエストに対してプロキシのバイパスルールを再評価するため、悪意のあるサーバーは、巧妙に作成されたリダイレクトの`Location`を通じて、この同期処理をトリガーできます。

この問題は、環境変数プロキシ変数(`NO_PROXY`)を使用し、リダイレクトを許可するNode.jsアプリケーションに影響を与えます。

## 影響

攻撃者が制御するサーバーは、axiosプロセスが同期的にホスト名の正規化に多大なCPU時間を費やさせるようなホスト名を含むリダイレクトを返す可能性があります。この間、Node.jsのイベントループがブロックされ、アプリケーションは別のスレッドで他の処理を実行できません。

これは可用性に影響を与える問題であり、リクエストデータは開示されず、リクエストも変更されません。

## 影響を受ける機能

影響を受けるもの:

- Node.js HTTPアダプタ。
- `HTTP_PROXY`または`HTTPS_PROXY`を介した環境プロキシの処理。
- `NO_PROXY`または`no_proxy`が空でない値に設定されている場合。
- axiosまたは`follow-redirects`によって処理されるリダイレクト。

影響を受けないもの:

- ブラウザアダプタ。
- `proxy: false`が設定されたリクエスト。
- `NO_PROXY`の値が設定されていないリクエスト。
- `maxRedirects: 0`が設定されたリクエスト(ただし、アプリケーションコードが意図的に悪意のあるリダイレクトに従い、再度axiosを呼び出す場合を除く)。

## 技術的な詳細

`lib/helpers/shouldBypassProxy.js`には以下のコードが含まれています。

```js
return unmapIPv4MappedIPv6(hostname.replace(/\.+$/, ''));
```

ホスト名が`"." * n + "a"`の場合、`$`アンカーにより、正規表現エンジンは、失敗する前に、多数の位置からドットの処理を再試行します。`setProxy()`は、`getProxyForUrl(location)`がプロキシを返す場合に、`shouldBypassProxy(location)`を呼び出します。これは、`beforeRedirects.proxy`を介したリダイレクトのホップでも同様です。

axios `1.18.1` におけるローカルでの検証では、作成されたホスト名に対する処理コストが増加していることが確認されました。具体的には、1000個のドットを含むホスト名では約1ミリ秒、3000個のドットでは6.9ミリ秒、6000個のドットでは34.5ミリ秒かかりました。この増加は、提出された二次関数の主張と一致しており、同時に長時間の処理を伴うペイロードを回避しています。

## 攻撃の概念実証

制限されたヘルパーレベルでのデモンストレーション:

```js
import shouldBypassProxy from 'axios/lib/helpers/shouldBypassProxy.js';

process.env.NO_PROXY = 'example.com';
shouldBypassProxy('http://' + '.'.repeat(6000) + 'a/');
```

完全なアダプターパスにおいて、悪意のあるサーバーは、クライアントがプロキシ環境変数と `NO_PROXY` を設定している場合に、`302 Location` ヘッダーで作成されたホスト名を返す可能性があります。

## 対策

信頼できないサーバーへのリクエストに対して、自動リダイレクトを無効にするか、`proxy: false` を適切に使用して、これらのリクエストに対する環境変数を利用したプロキシ処理を回避してください。また、イベントループの可用性が重要なサービスでは、広範囲な信頼できないリダイレクト追跡を避けることもできます。

<details>
<summary><h3>元のレポート</h3></summary>

### 概要

`shouldBypassProxy` は、`hostname.replace(/\.+$/, '')` を使用してホスト名を正規化します。`<多くのドット><ドット以外の文字>` の形式のホスト名(例:"." × 40000 + "a")の場合、このアンカー付きの正規表現は二次関数的に(O(N²))動作し、Node.js のイベントループを同期的にブロックします。axios は、新しい Location ホストを使用して、すべてのリダイレクトでプロキシを再評価するため、悪意のあるサーバーは作成された `302 Location` を返し、クライアントのイベントループをリダイレクトごとに数秒間停止させることで、サービス拒否攻撃を引き起こす可能性があります。

### 詳細

- 影響を受けるコード: lib/helpers/shouldBypassProxy.js → normalizeNoProxyHost: hostname.replace(/\.+$/, '') (バージョン1.18.1では1回のみ)。未解決のプルリクエスト#11029では、normalizeIPAddressに2回目の正規表現 /\.+$/ が追加されており、処理コストが2倍になっています(これは元のコードではありません)。
- 根本原因: 長いドットの連続が文字列の末尾でない場合、正規表現 /\.+$/ はO(N²)の計算量になります。なぜなら、$というアンカーが、エンジンにドットの連続全体を、すべての開始位置から遡らせることを強制するからです。
- 信頼境界(THREATMODELに基づく):リダイレクトのLocationは信頼できません(T-2:「axiosからネットワークへ…リダイレクトのLocation…信頼できない」)。呼び出し元は安全なURLを要求しますが、悪意のあるホストがサーバーの302リダイレクトを通じて到達します。これは、T-1の「呼び出し元が提供するURLではない」というケースとは異なります。
- 呼び出しシーケンス: setProxy(options, configProxy, location, isRedirect, …) (lib/adapters/http.js) → env-proxyブランチ → getProxyForUrl(location)がプロキシを返す → if (!shouldBypassProxy(location)) → normalizeNoProxyHost(parsed.hostname.toLowerCase())が正規表現 /\.+$/ を実行する。setProxyは、信頼できないLocationとともにリダイレクトの段階で再呼び出しされます。new URL()は、長いドットの連続を.hostnameに保持します。
- 前提条件: プロキシが環境変数(HTTP_PROXY/HTTPS_PROXY、THREATMODEL T-3に基づく、信頼済みであり、CI/コンテナ/エンタープライズ環境で一般的)で設定されていること + NO_PROXYが設定されていること + リダイレクトが実行されること(デフォルトの最大リダイレクト数: 5)。

### PoC (概念実証)

スタンドアロン環境、ネットワーク接続なし — axiosのヘルパー関数を使用:
// node poc.mjs (axiosをインストールした環境で実行)
import sbp from './node_modules/axios/lib/helpers/shouldBypassProxy.js';
process.env.NO_PROXY = 'example.com';
for (const n of [5000, 10000, 20000, 40000]) {
const url = 'http://' + '.'.repeat(n) + 'a/'; // 信頼できない302リダイレクトのLocationヘッダーとして到達
const t0 = process.hrtime.bigint();
sbp(url); // falseを返す (正しい、プロキシをバイパスしない) — しかし、O(N^2)の計算時間
console.log(`N=${n}: ${(Number(process.hrtime.bigint()-t0)/1e6)|0} ms`);
}
// 1.18.1で測定: N=5000 ~43ms, 10000 ~160ms, 20000 ~618ms, 40000 ~2499ms; 通常のホスト ~0.05ms。
実際のhttp-adapterの__setProxy関数が、リダイレクトパスで呼び出される (setProxy(…, isRedirect=true))。N=40000の場合、約2.4秒の間に10msのタイマーが0回実行される — イベントループが完全に停止。

### 影響

環境変数NO_PROXYが設定され、リダイレクトに従うaxiosクライアントにおいて、サーバーが不正なリダイレクトLocationヘッダーを返す場合に、サービス拒否 (イベントループの停止) が発生する可能性があります。データ漏洩やリモートコード実行のリスクはありません。影響のクラスは、GHSA-62hf-57xw-28j9 (toFormDataの再帰) や、maxContentLengthによるレスポンスサイズDoS攻撃と同様です。

### 修正案

正規表現による末尾のドット除去を、線形的なトリム処理に置き換えます。
let end = hostname.length;
while (end > 0 && hostname.charCodeAt(end - 1) === 46 /* '.' */) end--;
hostname = hostname.slice(0, end);
また、PR #11029のnormalizeIPAddressにおける冗長な正規表現 /\.\+$/ の2回目のパスも削除します。

---

影響を受けるパッケージ:
- npm (JavaScript) axios: 影響範囲は1.15.0以上、1.20.0未満。1.20.0で修正済み。