Next.jsのキャッシュ機構と CVE-2024-46982 技術詳細レポート

## Next.js のキャッシュ機構の挙動

### Next.jsのキャッシュ管理の基本
Next.jsでは、ページの生成方法によってデフォルトのキャッシュ挙動が異なります。静的サイト生成(SSG)されたページは原則として長期間キャッシュ可能で、サーバーは応答に公共キャッシュ用のヘッダを付与します。一方、サーバーサイドレンダリング(SSR)によるページは動的データを含むため**基本的にキャッシュされません**。例えばNext.jsのデフォルトでは、SSRページには`Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate`というヘッダが付き、一切キャッシュしないよう指示します ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=Default%20SSR%20Cache,revalidate))。対照的にSSGページでは`Cache-Control: s-maxage=31536000, stale-while-revalidate`といった長時間のキャッシュ許可ヘッダが付きます ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=Default%20SSR%20Cache,revalidate))。このように、Next.jsは**動的ページはキャッシュせず、静的ページのみをキャッシュする**のが基本方針です。

もっとも、Next.jsにはSSR結果をキャッシュするための方法も用意されています。開発者が意図的に`getServerSideProps`内でHTTPヘッダを設定すれば、SSRページでもキャッシュを有効化できます ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=,it%20for%20certain%20edge%20cases)) ([Data Fetching: getServerSideProps | Next.js](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props#edge-cases#:~:text=export%20async%20function%20getServerSideProps%28,revalidate%3D59%27))。しかし公式ドキュメントでも「SSRでキャッシュ制御を使う前に、ISRなど静的生成への移行を検討すべき」と注意されています ([Data Fetching: getServerSideProps | Next.js](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props#edge-cases#:~:text=However%2C%20before%20reaching%20for%20%60cache,fit%20for%20your%20use%20case))。つまりSSRページのキャッシュは**例外的なケース**として扱われ、通常はISRなど静的化の仕組みで対応するのが望ましいとされています。

### Stale-While-Revalidate (SWR)戦略の適用
Next.jsがキャッシュ制御で活用している戦略の一つに**Stale-While-Revalidate (SWR)**があります。SWRはHTTPのCache-Control拡張で、**キャッシュが期限切れになった後も、その古いレスポンスをクライアントに即座に提供しつつ、バックグラウンドで新しいレスポンスを取得する**方式です ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=stale))。これによりユーザは古い内容でもすぐページを表示でき、裏側では次のリクエストに備えてデータを更新できる利点があります ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=From%20RFC%205861%3A))。

Next.jsではSSGやISRでこのSWR戦略を活用しています。例えばデフォルトのSSGページは`stale-while-revalidate`付きで1年間キャッシュ可能なヘッダを持ちます ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=Default%20SSR%20Cache,revalidate))。ISR（増分的静的再生成）では、ビルド時に生成した静的ページに有効期限を設け、期限が切れた後の最初のアクセスでバックエンド再生成を走らせつつ、古いページを即時返すことでユーザ体験を損ねないようにしています。このとき内部的にはSWRと同様の動作が行われます。

開発者がSSRページでSWRを適用することも可能です。Next.js公式ドキュメントによれば、`getServerSideProps`内で以下のように`Cache-Control`ヘッダをセットすると、SSRの応答を短期間キャッシュしつつSWRを利用できます ([Data Fetching: getServerSideProps | Next.js](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props#edge-cases#:~:text=export%20async%20function%20getServerSideProps%28,revalidate%3D59%27)):

```js
export async function getServerSideProps({ req, res }) {
  // キャッシュを10秒間新鮮に保ち、59秒間は再検証しながら古い値を使用する
  res.setHeader(
    'Cache-Control',
    'public, s-maxage=10, stale-while-revalidate=59'
  )
  return { props: { /* ... */ } }
}
``` 

上記の例では、生成されたSSRページが**10秒間は新鮮(s-maxage=10)**とみなされ、その後最大59秒間はバックグラウンド再検証を許しつつ古いレスポンスを使用できます ([Data Fetching: getServerSideProps | Next.js](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props#edge-cases#:~:text=%2F%2F%20This%20value%20is%20considered,will%20see%20the%20new%20value)) ([Data Fetching: getServerSideProps | Next.js](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props#edge-cases#:~:text=res.setHeader%28%20%27Cache,))。このようにSWR戦略を適用すれば、SSRでも短時間のキャッシュが可能ですが、キャッシュ適用範囲の慎重な設計が必要です。

### キャッシュのチェーン処理と影響
Webアプリケーションでは**キャッシュが多層(チェーン)になっている**場合があります。Next.jsアプリを運用する際、アプリケーションサーバ（Next.js自体）の前段にCDNやリバースプロキシが置かれ、これらもコンテンツをキャッシュすることが一般的です。こうした**多段キャッシュ環境**では、各層のキャッシュキーの扱い方が重要になります。

例えば、ブラウザからのリクエストURLが`https://www.example.com/`であった場合と、あるクエリパラメータ付きで`https://www.example.com/?param=1`であった場合、CDNがその`param`をキャッシュキーとして区別しない設定だと両者のレスポンスが混同される恐れがあります ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=In%20our%20case%2C%20to%20trigger,differentiate%20between%20these%20two%20requests))。つまり、攻撃者が特殊なクエリ付きリクエストBで取得・キャッシュさせたレスポンスが、パラメータ無しの通常リクエストAに対して返されてしまうといった事態が起こりえます ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=the%20target%20site%20must%20have,differentiate%20between%20these%20two%20requests))。このように**上流のキャッシュ層がリクエストの差異を適切に識別しない**場合、キャッシュ汚染による不正なレスポンス提供が発生します。

Next.jsでは、先述のSWR戦略に関連して**`Vary`ヘッダ**も活用しています。`Vary`ヘッダは「リクエストのどの要素がレスポンス内容に影響したか」をキャッシュに伝えるもので、これによりCDNは適切にキャッシュキーを分割できます。しかしCDNの設定や実装によっては`Vary`ヘッダが無視されたり、クエリパラメータを無視するケースもあります ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=If%20the%20,negotiation%20phase)) ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=The%20maintainers%20of%20next,not%20adhere%20to%20these%20requirements))。実際Next.jsチームは、React Server Components(RSC)のペイロードが誤ってCDNにキャッシュされる問題に対し、一旦`Vary`ヘッダで対応したものの効果が不十分で、最終的にURLに乱数パラメータ`_rsc=`を付与することで強制的にキャッシュキーを変える対策を行いました ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=By%20inspecting%20my%20HTTP%20history,rsc%3DrandomValue)) ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=,is%20a%20CDN%20configuration%20problem))。この事例からも、多段キャッシュ環境では**キャッシュキーの一貫性管理**（ヘッダやパラメータによる識別）が重要であり、適切に設定しないと予期せぬキャッシュ衝突や汚染が発生することが分かります。

### SSRとISRにおけるキャッシュの違い
**SSR (Server-Side Rendering)** と **ISR (Incremental Static Regeneration)** は、Next.jsでデータを提供する二つの主要な方式であり、キャッシュの取り扱いが大きく異なります。

- **SSR**: リクエストごとにサーバーでページを生成する方式です。各リクエスト時に`getServerSideProps`等が実行され最新データを反映します。そのため**デフォルトではキャッシュしません** ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=Default%20SSR%20Cache,revalidate))。すべてのクライアントに常に最新情報を提供できますが、リクエスト数増加時の負荷は高くなります。必要に応じて前述の通り開発者判断で短期キャッシュを導入できますが、原則としては**都度生成・都度配信**です。

- **ISR**: 静的生成(SSG)と動的更新を組み合わせた方式です。初回またはビルド時にページを静的生成しキャッシュしますが、一定時間（開発者が設定する再生成間隔 `revalidate`）が過ぎると次のリクエスト時にバックエンドでページを再生成します ([Data Fetching: getServerSideProps | Next.js](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props#edge-cases#:~:text=However%2C%20before%20reaching%20for%20%60cache,fit%20for%20your%20use%20case))。再生成リクエストが発生した場合でも、SWR戦略により古いキャッシュを即座に返してユーザを待たせず、その裏で新ページを作成・キャッシュ更新します。これにより**高速な配信とデータ更新の両立**を図っています。ISRの静的ページは`Cache-Control: s-maxage=<秒数>, stale-while-revalidate=<秒数>`ヘッダを持ち ([Data Fetching: getServerSideProps | Next.js](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props#edge-cases#:~:text=export%20async%20function%20getServerSideProps%28,revalidate%3D59%27))、CDNがその指示に従ってキャッシュ運用できるようになっています。

要するに、**SSRは毎回生成 (デフォルト非キャッシュ)**、**ISRは基本静的キャッシュしつつ期限到来でバックグラウンド再生成**という違いがあります。Next.jsでは動的性が低いページはISR/SSGでキャッシュ性を高め、ユーザごとに大きく異なるページ（例: ダッシュボード等ログインユーザ依存ページ）はSSRで都度生成する、といった使い分けが推奨されます。

## CVE-2024-46982 の詳細分析

### 脆弱性の技術的背景
**CVE-2024-46982**は、Next.jsにおける**キャッシュポイズニング（キャッシュ汚染）**の脆弱性です。この脆弱性の背景には、本来キャッシュ不可のSSRページを誤ってキャッシュ可能と判断してしまうNext.js内部の挙動があります。

発端はNext.js内部で使われている**内部ヘッダ `x-now-route-matches`**の存在です。本来、Next.js（特にVercel環境）内部でルーティング情報を伝達するためのヘッダですが、これを外部からリクエストに送りつけることで**SSRリクエストをSSGリクエストだと誤認させる**ことが可能でした ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=With%20the%20basics%20laid%20out%2C,Interesting%20isn%E2%80%99t%20it)) ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=req.headers%5B%27x))。Next.jsのコード上、このヘッダが存在すると`isSSG`フラグがtrueになり、SSRページにもかかわらず「静的生成ページとして扱う」処理が走ります ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=With%20the%20basics%20laid%20out%2C,Interesting%20isn%E2%80%99t%20it))。その結果、**本来キャッシュ禁止のSSR応答に対し、キャッシュ可能なヘッダが付与されてしまう**のです ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=therefore%20supposed%20,control%60))。

具体的には、攻撃者が`getServerSideProps`を用いるページに対し細工したリクエストを送ると、Next.jsは応答ヘッダに`Cache-Control: s-maxage=1, stale-while-revalidate`を付与します ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=Now%20that%20the%20request%20is,Control))。通常そのページでは付与されないはずのヘッダであり、**「このページは1秒間だけキャッシュ可能（共有キャッシュ向け）、以後はステールだが再検証可」**という指示となります。この状態が今回の脆弱性の下地です。

さらにNext.jsには、ページ遷移時にJSONデータをやり取りする仕組みがあります。SSRページであっても、クエリパラメータ`__nextDataReq=1`を付与すると、HTMLではなくJSON形式のページデータ（`pageProps`など）を返すモードになります ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=1,on%20the%20page%20in%20question)) ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=Thus%2C%20by%20sending%20a%20request,true))。これは通常、クライアント側のページ遷移（Next.jsのリンククリック）で使われる内部APIですが、外部から直接リクエストすることも可能です。攻撃者はこの`__nextDataReq`パラメータと前述の`x-now-route-matches`ヘッダを組み合わせることで、**SSRページのJSONデータにキャッシュ可能ヘッダを付与させる**ことができます ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=By%20combining%20in%20the%20request%3A)) ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=1,Control))。

まとめると、この脆弱性は**「内部フラグ用ヘッダの濫用によるSSR応答の誤キャッシュ化」**という技術的背景を持ちます。その結果、攻撃者が細工したリクエストを送るだけで、本来キャッシュされないはずのSSRページがキャッシュされてしまうのです。

### 影響範囲と悪用可能性
この脆弱性が影響を及ぼす条件は限定的ですが、該当すると深刻な被害を招く可能性があります。Next.js公式アドバイザリによれば、**以下の全ての条件**を満たす場合に脆弱性が成立します ([CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js](https://ogma.in/cve-2024-46982-understanding-and-mitigating-cache-poisoning-in-next-js#:~:text=The%20conditions%20required%20for%20this,vulnerability%20to%20be%20exploitable%20are)):

- Next.js のバージョンが **13.5.1 以上 14.2.9 以下**（この期間の特定バージョンに存在） ([CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js](https://ogma.in/cve-2024-46982-understanding-and-mitigating-cache-poisoning-in-next-js#:~:text=,slug%5D.tsx))。
- **Pagesルーター**を使用している（新しいAppルーターには影響なし） ([CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js](https://ogma.in/cve-2024-46982-understanding-and-mitigating-cache-poisoning-in-next-js#:~:text=The%20vulnerability%2C%20identified%20by%20GitHub_M,upstream%20CDNs%20may%20mistakenly%20cache))。
- **動的でないSSRルート**を使用（例：`pages/dashboard.tsx`のようにURLパラメータを持たないSSRページ。`pages/blog/[slug].tsx`のような動的ルートは対象外） ([NVD - CVE-2024-46982](https://nvd.nist.gov/vuln/detail/CVE-2024-46982#:~:text=dynamic%20server,There%20are))。

上記を満たすNext.jsアプリケーションでは、悪意あるリクエストによりキャッシュポイズニングが発生しえます。具体的な**悪用シナリオ**として、以下のような影響が報告されています:

- **サービス妨害（DoS）**: 攻撃者が仕掛けたキャッシュ汚染により、対象ページに本来のHTMLではなく無意味なJSONオブジェクトが返されてしまい、利用者はページ内容を閲覧できなくなります ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=anything%3A%20,o%20m))。攻撃者が定期的にキャッシュを汚染し続ければ、該当ページは事実上ダウンした状態になります。
- **ストアドXSS**: SSRページがユーザのリクエスト情報（User-Agentやクッキー値など）をページに埋め込んでいる場合、攻撃者はその部分にスクリプトを仕込んでキャッシュさせることで、他の利用者に対し**悪意あるスクリプトを含んだHTML**を配信できます ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=response%20%28,type%60%20is%20%60text%2Fhtml)) ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=This%20means%20that%20any%20value,cookies%2C%20headers%2C%20URL%20parameters%2C%20etc))。一度キャッシュに載ってしまえば、次にそのページにアクセスしたユーザのブラウザ上でスクリプトが実行されるため、典型的なStored XSS（永続的クロスサイトスクリプティング）となります。
- **機密情報の漏洩**: SSRページがログインユーザ固有のデータを表示する場合、キャッシュ汚染によってあるユーザのデータが他のユーザに表示されてしまう恐れもあります。攻撃者自身が閲覧権限のない情報を引き出すことは難しいものの、例えば管理者が閲覧したページを攻撃者が汚染しキャッシュすると、不特定多数にその内容が漏洩する可能性があります。
- **その他の副次被害**: キャッシュ汚染によりHTTPステータスコードまでも汚染されるケースも報告されています。実際に、攻撃リクエストで内部エラー（500）を発生させそれがキャッシュされてしまい、以降そのページがずっと500エラーを返すといった現象も確認されています ([Is it safe to stripe off header 'X-Now-Route-Matches' before forwarding request to NextJs : r/nextjs](https://www.reddit.com/r/nextjs/comments/1fp5oeb/is_it_safe_to_stripe_off_header_xnowroutematches/#:~:text=What%20is%20the%20use%20of,server%20response%20until%20cache%20expires))。

以上のように、**攻撃難易度は低く（HTTPリクエストを送るだけ）**かつ**結果は深刻**であるため、CVSS基本値は7.5（高深刻度）に分類されています ([CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js](https://ogma.in/cve-2024-46982-understanding-and-mitigating-cache-poisoning-in-next-js#:~:text=Due%20to%20the%20low%20attack,5%2C%20categorized%20as%20High%20severity))。幸い条件を満たす環境は限定されていますが、**該当バージョンを使用しているサイトにとっては重大なリスク**となります。

### Next.jsのキャッシュ機構との関係
CVE-2024-46982は、Next.jsのキャッシュ機構の内部動作を突いた攻撃でした。本来、Next.jsは前述の通りSSRページをキャッシュしない設計ですが、その**設計の裏をかく形でキャッシュ層を騙す**のが本脆弱性のポイントです。

内部ヘッダ`x-now-route-matches`を悪用された結果、Next.jsはSSRリクエストをSSG扱いし、`s-maxage=1, stale-while-revalidate`というキャッシュ許可ヘッダを付けてしまいました ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=therefore%20supposed%20,control%60))。**このヘッダが付与されたこと自体はNext.js内部の問題**ですが、それだけでは即座に他のユーザへの被害は起きません。実際、攻撃者本人がそのレスポンス（JSONデータ）を受け取るだけなら被害は限定的です。

しかし問題は、その**レスポンスがキャッシュチェーン上でどのように扱われるか**にあります。典型的にはNext.jsサーバの前段にあるCDNなどの**共有キャッシュ**がそれを保存し、後続の通常リクエストに対して提供してしまうことが被害の引き金となります ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=the%20target%20site%20must%20have,differentiate%20between%20these%20two%20requests))。すなわち、**「本来キャッシュキーが異なるはずのリクエストAとBが同一視され、攻撃者Bの応答が無関係なAに返る」**という状況が生まれるのです ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=differentiate%20between%20these%20two%20requests%3A))。

今回のケースでは、`__nextDataReq=1`というパラメータ付きリクエストと、通常のリクエストでURLが異なります。しかしCDNの設定によってはクエリパラメータを無視してキャッシュすることがあります。その場合、攻撃者のJSON応答が**通常ページのキャッシュ**として蓄積され、以降パラメータ無しでアクセスしてきたユーザに対してJSONが返されてしまいました ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=And%20now%20when%20I%20access,b%20o%20o%20m))。さらにContent-Typeまでも誤った状態（HTMLとして扱われる）で提供されていたため、前述のXSSのような深刻な副作用も生じました ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=Now%2C%20the%20sharpest%20among%20you,type%60%20is%20%60text%2Fhtml)) ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=response%20%28,type%60%20is%20%60text%2Fhtml))。

加えて、Next.jsが**複数の内部ヘッダ**を持っている点も関係します。今回悪用された`x-now-route-matches`以外にも、`x-invoke-status`等の内部ヘッダが存在し、過去の調査ではこれらが応答内容（ステータスコードやエラーページ）に影響を与えることが指摘されています ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=Any%20element%20that%20can%20be,7))。Next.js v14.2.7では`x-invoke-status`ヘッダの悪用が修正されており、今回のv14.2.10では`x-now-route-matches`の問題が修正された形です ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=Any%20element%20that%20can%20be,7)) ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=the%20%60x,7))。このように、Next.js内部のキャッシュ判断ロジックが外部から影響を受けたことが根本原因であり、**キャッシュ機構の設計上の想定外利用**と言えます。

### エクスプロイト事例と PoC
CVE-2024-46982に対しては、セキュリティ研究者による詳細な調査とPoC（概念実証コード）が公開されています。GitHub上にはこの脆弱性を検出・悪用する自動化ツールも登場しており、具体的な攻撃手順が示されています ([GitHub - Lercas/CVE-2024-46982: POC CVE-2024-46982](https://github.com/Lercas/CVE-2024-46982#:~:text=Next))。その内容によれば、本脆弱性を悪用することで**「Next.jsのSSRレスポンスをSSGとして誤キャッシュさせ、Stored XSSやDoS、データ漏洩を引き起こす」**ことが可能だと説明されています ([GitHub - Lercas/CVE-2024-46982: POC CVE-2024-46982](https://github.com/Lercas/CVE-2024-46982#:~:text=Next))。

代表的な攻撃手順は以下の通りです ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=By%20combining%20in%20the%20request%3A)) ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=1,Control)):

1. 標的のSSRページエンドポイント（例: `/poc`）に対し、クエリパラメータ`__nextDataReq=1`を付与する。これによりサーバはページをJSON形式で返すモードになる。
2. 同じリクエストに内部ヘッダ`x-now-route-matches: 1`を付けて送信する。これでNext.jsはそのリクエストをSSG扱いし、キャッシュ制御ヘッダを付与したJSONレスポンスを返す。
3. 攻撃者は上記リクエストをタイミングよく（キャッシュ有効期間が切れた直後など）送り、CDNに悪意あるJSONレスポンスをキャッシュさせる。必要ならスクリプトで連続リクエストし、この状態を安定して維持する ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=Ok%2C%20so%20caching%20is%20possible,sufficient%20to%20%E2%80%9Cstabilize%E2%80%9D%20its%20poisoning))。
4. 他のユーザが通常のブラウザで対象ページ(`/poc`)にアクセスすると、キャッシュされた不正レスポンスが返却される。ユーザにはページ内容が表示されずDoS状態となったり、埋め込まれたスクリプトが実行されてXSSとなったりする ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=anything%3A%20,o%20m)) ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=response%20%28,type%60%20is%20%60text%2Fhtml))。

実際に、この脆弱性は**多くのサイトで再現可能**であったと報告されています。とくに「独自にCDNを設定しており、URLパラメータをキャッシュキーに含めていない」「Vercel以外にホスティングしている」ケースで軒並み脆弱だったとのことです ([Next.js, cache, and chains: the stale elixir - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir#:~:text=Real%20world%20exploitation%20))。研究者は複数のバグバウンティプログラムで本脆弱性を報告し、報奨金を得たことも明らかにしています ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=Excited%20by%20the%20idea%20of,me%20to%20continue%20my%20research)) ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=assigned%20nvd.nist.gov%20%20%28CVE,me%20to%20continue%20my%20research))。

以上の事例から、**攻撃の現実性は非常に高い**と言えます。公開されたPoCツールでは、User-AgentヘッダにXSSペイロードを含めてキャッシュ汚染し、実際に他ユーザでアラートを表示させるといった攻撃が自動化されています ([GitHub - Lercas/CVE-2024-46982: POC CVE-2024-46982](https://github.com/Lercas/CVE-2024-46982#:~:text=Create%20a%20file%20named%20,Agent%20header)) ([GitHub - Lercas/CVE-2024-46982: POC CVE-2024-46982](https://github.com/Lercas/CVE-2024-46982#:~:text=How%20It%20Works))。今後もパッチ未適用のサイトに対しては、同様の攻撃が行われる可能性があるため注意が必要です。

### 修正方法と対策
Next.js開発チームは本脆弱性を認識し、**2024年9月18日**付けで修正版をリリースしました ([Next.js Cache Poisoning · CVE-2024-46982 · GitHub Advisory Database · GitHub](https://github.com/advisories/GHSA-gp8f-8m3g-qvj9#:~:text=High%20severity%20%20GitHub%20Reviewed,%E2%80%A2%20Updated%20Sep%2018%2C%202024)) ([Next.js Cache Poisoning · CVE-2024-46982 · GitHub Advisory Database · GitHub](https://github.com/advisories/GHSA-gp8f-8m3g-qvj9#:~:text=Patches))。以下のバージョンで問題が修正されています ([CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js](https://ogma.in/cve-2024-46982-understanding-and-mitigating-cache-poisoning-in-next-js#:~:text=The%20affected%20versions%20of%20Next,are)):

- **Next.js 13 系**: v13.5.7  
- **Next.js 14 系**: v14.2.10  

そのため、最も確実で推奨される対策は**該当バージョン以降へのアップグレード**です ([CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js](https://ogma.in/cve-2024-46982-understanding-and-mitigating-cache-poisoning-in-next-js#:~:text=To%20mitigate%20the%20risk%20posed,46982%2C%20follow%20these%20steps))。Next.js公式も「問題を再現できるかに関わらず、すみやかに安全なバージョンへ更新すること」を強く推奨しています ([NVD - CVE-2024-46982](https://nvd.nist.gov/vuln/detail/CVE-2024-46982#:~:text=potentially%20affected%20all%20of%20the,patch%20to%20a%20safe%20version))。

また、Next.jsから公式に案内されている**ワークアラウンド（回避策）は存在しません】 ([NVD - CVE-2024-46982](https://nvd.nist.gov/vuln/detail/CVE-2024-46982#:~:text=routes%20e.g.%20%60pages%2Fdashboard.tsx%60%20not%20%60pages%2Fblog%2F,patch%20to%20a%20safe%20version))。唯一の根本対処はアップデートとなります。とはいえ、アップデートがすぐに適用できない場合の**一時的な緩和策**として、以下のような対処が考えられます:

- **リバースプロキシ/CDN側でヘッダ・パラメータをフィルタ**: 外部からのリクエストについて、`x-now-route-matches`ヘッダを受け付けないように削除する、`__nextDataReq`パラメータ付きリクエストを遮断する、などのルールを設けます。これによりNext.jsまで不正リクエストを届かせないようにできます（※ただし根本的な修正ではなく応急処置です）。
- **CDNのキャッシュキー設定を見直し**: 少なくとも`__nextDataReq`のような内部API用パラメータがキャッシュキーから無視されないよう設定します。あるいは今回のようなHTMLとJSONでContent-Typeが異なる応答を区別するため、`Accept`ヘッダや`Content-Type`を考慮したキャッシュ分離を行います（Next.jsが付与する`Vary`ヘッダを正しく尊重する設定にする）。
- **対象ページの構成変更**: 脆弱性条件となる「非動的SSRページ」に該当するページを見直します ([CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js](https://ogma.in/cve-2024-46982-understanding-and-mitigating-cache-poisoning-in-next-js#:~:text=or%20later,to%20route%20configurations%20if%20necessary))。もし一部のSSRページが実は動的性を必要としないのであれば、`getStaticProps`+ISRに切り替えて脆弱性範囲から外すことも検討できます。またどうしてもSSRが必要なページでは、`getServerSideProps`内で明示的に`res.setHeader('Cache-Control', 'private, no-store, ...')`を再設定しておくなど、防御的な実装も一案です。

しかしこれらはいずれも**公式に保証された解決策ではなく、可能な限定的対処**に過ぎません。やはり推奨されるのは**早急なNext.jsアップデート**と、アップデート適用までの間サイトを慎重に監視することです。加えて、依存するホスティング環境（例えばVercelでは本問題が発生しなかったとの報告あり ([Next.js Cache Poisoning · CVE-2024-46982 · GitHub Advisory Database · GitHub](https://github.com/advisories/GHSA-gp8f-8m3g-qvj9#:~:text=The%20below%20configurations%20are%20unaffected%3A))）も踏まえ、自社環境で追加すべき防御策がないか検討してください。

## 関連する脆弱性と今後の対応

### 類似の既知脆弱性
Next.jsでは、キャッシュに起因する類似の脆弱性が過去にも報告されています。直近の例として**CVE-2023-46298**が挙げられます。この脆弱性はNext.js 13.4.20以前に存在したもので、**SSRページのプレフェッチ要求**において発生しました ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=I%20quickly%20discovered%20an%20interesting,impractical%20and%20its%20content%20inaccessible))。具体的には、`x-middleware-prefetch`というヘッダを付けてSSRページのデータを事前取得しようとすると、サーバは内容の無い空のJSON `{}` を返します ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=I%20quickly%20discovered%20an%20interesting,impractical%20and%20its%20content%20inaccessible))。問題はこの応答にCache-Controlヘッダが適切に付与されていなかったため、CDNによってはその空のレスポンスがキャッシュされてしまい、結果として本来のページコンテンツが表示されなくなるというものでした ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=When%20prefetching%20data%20for%20SSR,impractical%20and%20its%20content%20inaccessible)) ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=I%20won%E2%80%99t%20delve%20into%20the,prevent%20them%20from%20being%20cached))。この欠陥もまた**キャッシュポイズニングによるDoS**の一種であり、CVSS7.5に評価されています ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=Impact%20and%20severity))。Next.jsチームはこの問題に対し、SSRレスポンスにCache-Controlヘッダを付与する変更を行い（v13.4.20-canary.13で修正 ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=and%20generate%20static%20pages%2C%20respectively,prevent%20them%20from%20being%20cached))）、同様のプレフェッチによるキャッシュ汚染を防止しました。

また、今回のCVE-2024-46982を報告した研究者は、Next.jsにおける複数のキャッシュ関連問題を調査しており、その中で**React Server Components (RSC)とCDNの関係**にも触れています ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=During%20my%20research%20on%20Next,poisoning))。RSCリクエストでは`Rsc: 1`ヘッダが用いられ、Next.jsはこれに基づき`Vary`ヘッダを設定していましたが、一部CDNがこれを正しく扱わずRSCペイロードをHTMLページとしてキャッシュする事態が起きていました ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=The%20header%20responsible%20for%20this,poisoning)) ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=If%20the%20,negotiation%20phase))。Next.js側はこれに対し、先述のとおり内部的に`_rsc`というキャッシュバスター用のクエリを導入する対応を取っています ([Next.js and cache poisoning: a quest for the black hole - zhero_web_security](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#:~:text=By%20inspecting%20my%20HTTP%20history,rsc%3DrandomValue))。このように、Next.jsとCDNの組み合わせにおいて**キャッシュキーの不整合**から生じる問題は他にも存在しました。

さらに、Next.jsに限らず**ウェブキャッシュポイズニング**自体は古くから知られる攻撃手法です。別のケースでは、HTTPレスポンス分割やヘッダインジェクションを利用してCache-Controlヘッダを操作しキャッシュを汚染する手法などもあります。Next.jsの場合、内部仕様の隙を突かれた形でしたが、根底には「キャッシュ層でリクエストAとBが区別できないと危険」という普遍的な問題があります。したがって、開発者や運用者は**他のパラメータやヘッダによるキャッシュ汚染リスク**にも常に注意を払う必要があります。

### Next.jsの今後の対応方針
Next.js開発陣は今回の脆弱性に迅速に対応し、パッチを提供しました。しかし今後も同様の問題を防ぐために、いくつかの方向性が考えられます。

まず、**内部ヘッダの取り扱い強化**です。`x-now-route-matches`にしろ`x-invoke-status`にしろ、基本的には外部から渡すべきではない値でした。今後のバージョンでは、これら内部用途のヘッダを**デフォルトで外部リクエストから無視または削除**する措置が取られる可能性があります。実際、他のフレームワークでは内部ヘッダは`X-Internal-...`のような名称にして明確化したり、プロキシ層で拒否したりする運用が一般的です。Next.jsも今回の教訓を踏まえ、**信頼できないリクエストから内部状態を変化させられないようにする**方向に動くでしょう。

次に、**キャッシュ機構の改良**です。Next.jsはAppルーター（React Server Componentsベース）への移行が進んでおり、こちらではPagesルーターで問題となった部分が影響しない設計でした ([CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js](https://ogma.in/cve-2024-46982-understanding-and-mitigating-cache-poisoning-in-next-js#:~:text=The%20vulnerability%2C%20identified%20by%20GitHub_M,upstream%20CDNs%20may%20mistakenly%20cache))。Appルーターでは`Vary`ヘッダの適切な使用など近代的なキャッシュ制御が導入されていますが、今後は**より明示的なキャッシュ制御**がなされる可能性があります。例えば、今回のように誤って静的扱いされた場合でも`max-age=0`を強制する保険的なチェックを入れる、CDNに対する注意喚起をドキュメントに追記する、といった対応が考えられます。

公式情報としては、GitHubのセキュリティアドバイザリやリリースノートに修正内容が記載されています ([Next.js Cache Poisoning · CVE-2024-46982 · GitHub Advisory Database · GitHub](https://github.com/advisories/GHSA-gp8f-8m3g-qvj9#:~:text=Patches))。そこでは該当コードの修正(commit)へのリンクも提示されており、実際にどのように問題が修正されたか追うことができます ([Next.js Security Update Advisory (CVE-2024-46982) - ASEC](https://asec.ahnlab.com/en/83326/#:~:text=))。コミット履歴によれば、問題の原因となった不適切な再検証値（`revalidate`のデフォルトが1になっていた等）を除去し、内部ヘッダの扱いを改善する変更が含まれています ([Remove invalid fallback revalidate value (#69990) · vercel/next.js@7ed7f12 · GitHub](https://github.com/vercel/next.js/commit/7ed7f125e07ef0517a331009ed7e32691ba403d3#:~:text=revalidate%3A%20result,1)) ([Remove invalid fallback revalidate value (#69990) · vercel/next.js@7ed7f12 · GitHub](https://github.com/vercel/next.js/commit/7ed7f125e07ef0517a331009ed7e32691ba403d3#:~:text=match%20at%20L257%20metadata,triggering%20legacy%20%2F%2F%20404%20rendering))。

最後に、開発者・利用者への注意喚起として**「最新の安全なバージョンへの更新の徹底」**が挙げられます。Next.js公式ブログやコミュニティでも、依存パッケージの定期的なアップデートやセキュリティ情報のチェックが推奨されています ([CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js](https://ogma.in/cve-2024-46982-understanding-and-mitigating-cache-poisoning-in-next-js#:~:text=To%20mitigate%20the%20risk%20posed,46982%2C%20follow%20these%20steps)) ([CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js](https://ogma.in/cve-2024-46982-understanding-and-mitigating-cache-poisoning-in-next-js#:~:text=Conclusion))。フレームワーク自体の脆弱性はアプリケーション全体に影響するため、今後もNext.jsに限らず依存ソフトウェアのアップデートを怠らないことが重要です。

以上、Next.jsのキャッシュ機構とCVE-2024-46982の詳細について技術的に解説しました。本脆弱性はキャッシュ戦略の盲点を突いたものでしたが、適切な対策を講じることで被害を防ぐことができます。開発者はキャッシュの挙動を正しく理解し、フレームワークのアップデートと設定確認を通じて、安全なWebアプリケーション運用を心がけてください。