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)。対照的にSSGページではCache-Control: s-maxage=31536000, stale-while-revalidate
といった長時間のキャッシュ許可ヘッダが付きます (Next.js, cache, and chains: the stale elixir - zhero_web_security)。このように、Next.jsは動的ページはキャッシュせず、静的ページのみをキャッシュするのが基本方針です。
もっとも、Next.jsにはSSR結果をキャッシュするための方法も用意されています。開発者が意図的にgetServerSideProps
内でHTTPヘッダを設定すれば、SSRページでもキャッシュを有効化できます (Next.js, cache, and chains: the stale elixir - zhero_web_security) (Data Fetching: getServerSideProps | Next.js)。しかし公式ドキュメントでも「SSRでキャッシュ制御を使う前に、ISRなど静的生成への移行を検討すべき」と注意されています (Data Fetching: getServerSideProps | Next.js)。つまり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)。これによりユーザは古い内容でもすぐページを表示でき、裏側では次のリクエストに備えてデータを更新できる利点があります (Next.js, cache, and chains: the stale elixir - zhero_web_security)。
Next.jsではSSGやISRでこのSWR戦略を活用しています。例えばデフォルトのSSGページはstale-while-revalidate
付きで1年間キャッシュ可能なヘッダを持ちます (Next.js, cache, and chains: the stale elixir - zhero_web_security)。ISR(増分的静的再生成)では、ビルド時に生成した静的ページに有効期限を設け、期限が切れた後の最初のアクセスでバックエンド再生成を走らせつつ、古いページを即時返すことでユーザ体験を損ねないようにしています。このとき内部的にはSWRと同様の動作が行われます。
開発者がSSRページでSWRを適用することも可能です。Next.js公式ドキュメントによれば、getServerSideProps
内で以下のようにCache-Control
ヘッダをセットすると、SSRの応答を短期間キャッシュしつつSWRを利用できます (Data Fetching: getServerSideProps | Next.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) (Data Fetching: getServerSideProps | Next.js)。このように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)。つまり、攻撃者が特殊なクエリ付きリクエストBで取得・キャッシュさせたレスポンスが、パラメータ無しの通常リクエストAに対して返されてしまうといった事態が起こりえます (Next.js, cache, and chains: the stale elixir - zhero_web_security)。このように上流のキャッシュ層がリクエストの差異を適切に識別しない場合、キャッシュ汚染による不正なレスポンス提供が発生します。
Next.jsでは、先述のSWR戦略に関連して**Vary
ヘッダも活用しています。Vary
ヘッダは「リクエストのどの要素がレスポンス内容に影響したか」をキャッシュに伝えるもので、これによりCDNは適切にキャッシュキーを分割できます。しかしCDNの設定や実装によってはVary
ヘッダが無視されたり、クエリパラメータを無視するケースもあります (Next.js and cache poisoning: a quest for the black hole - zhero_web_security) (Next.js and cache poisoning: a quest for the black hole - zhero_web_security)。実際Next.jsチームは、React Server Components(RSC)のペイロードが誤ってCDNにキャッシュされる問題に対し、一旦Vary
ヘッダで対応したものの効果が不十分で、最終的にURLに乱数パラメータ_rsc=
を付与することで強制的にキャッシュキーを変える対策を行いました (Next.js and cache poisoning: a quest for the black hole - zhero_web_security) (Next.js and cache poisoning: a quest for the black hole - zhero_web_security)。この事例からも、多段キャッシュ環境ではキャッシュキーの一貫性管理**(ヘッダやパラメータによる識別)が重要であり、適切に設定しないと予期せぬキャッシュ衝突や汚染が発生することが分かります。
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)。すべてのクライアントに常に最新情報を提供できますが、リクエスト数増加時の負荷は高くなります。必要に応じて前述の通り開発者判断で短期キャッシュを導入できますが、原則としては都度生成・都度配信です。 -
ISR: 静的生成(SSG)と動的更新を組み合わせた方式です。初回またはビルド時にページを静的生成しキャッシュしますが、一定時間(開発者が設定する再生成間隔
revalidate
)が過ぎると次のリクエスト時にバックエンドでページを再生成します (Data Fetching: getServerSideProps | Next.js)。再生成リクエストが発生した場合でも、SWR戦略により古いキャッシュを即座に返してユーザを待たせず、その裏で新ページを作成・キャッシュ更新します。これにより高速な配信とデータ更新の両立を図っています。ISRの静的ページはCache-Control: s-maxage=<秒数>, stale-while-revalidate=<秒数>
ヘッダを持ち (Data Fetching: getServerSideProps | Next.js)、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) (Next.js, cache, and chains: the stale elixir - zhero_web_security)。Next.jsのコード上、このヘッダが存在するとisSSG
フラグがtrueになり、SSRページにもかかわらず「静的生成ページとして扱う」処理が走ります (Next.js, cache, and chains: the stale elixir - zhero_web_security)。その結果、本来キャッシュ禁止のSSR応答に対し、キャッシュ可能なヘッダが付与されてしまうのです (Next.js, cache, and chains: the stale elixir - zhero_web_security)。
具体的には、攻撃者がgetServerSideProps
を用いるページに対し細工したリクエストを送ると、Next.jsは応答ヘッダにCache-Control: s-maxage=1, stale-while-revalidate
を付与します (Next.js, cache, and chains: the stale elixir - zhero_web_security)。通常そのページでは付与されないはずのヘッダであり、**「このページは1秒間だけキャッシュ可能(共有キャッシュ向け)、以後はステールだが再検証可」**という指示となります。この状態が今回の脆弱性の下地です。
さらにNext.jsには、ページ遷移時にJSONデータをやり取りする仕組みがあります。SSRページであっても、クエリパラメータ__nextDataReq=1
を付与すると、HTMLではなくJSON形式のページデータ(pageProps
など)を返すモードになります (Next.js, cache, and chains: the stale elixir - zhero_web_security) (Next.js, cache, and chains: the stale elixir - zhero_web_security)。これは通常、クライアント側のページ遷移(Next.jsのリンククリック)で使われる内部APIですが、外部から直接リクエストすることも可能です。攻撃者はこの__nextDataReq
パラメータと前述のx-now-route-matches
ヘッダを組み合わせることで、SSRページのJSONデータにキャッシュ可能ヘッダを付与させることができます (Next.js, cache, and chains: the stale elixir - zhero_web_security) (Next.js, cache, and chains: the stale elixir - zhero_web_security)。
まとめると、この脆弱性は**「内部フラグ用ヘッダの濫用によるSSR応答の誤キャッシュ化」**という技術的背景を持ちます。その結果、攻撃者が細工したリクエストを送るだけで、本来キャッシュされないはずのSSRページがキャッシュされてしまうのです。
影響範囲と悪用可能性
この脆弱性が影響を及ぼす条件は限定的ですが、該当すると深刻な被害を招く可能性があります。Next.js公式アドバイザリによれば、以下の全ての条件を満たす場合に脆弱性が成立します (CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js):
- Next.js のバージョンが 13.5.1 以上 14.2.9 以下(この期間の特定バージョンに存在) (CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js)。
- Pagesルーターを使用している(新しいAppルーターには影響なし) (CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js)。
-
動的でないSSRルートを使用(例:
pages/dashboard.tsx
のようにURLパラメータを持たないSSRページ。pages/blog/[slug].tsx
のような動的ルートは対象外) (NVD - CVE-2024-46982)。
上記を満たすNext.jsアプリケーションでは、悪意あるリクエストによりキャッシュポイズニングが発生しえます。具体的な悪用シナリオとして、以下のような影響が報告されています:
- サービス妨害(DoS): 攻撃者が仕掛けたキャッシュ汚染により、対象ページに本来のHTMLではなく無意味なJSONオブジェクトが返されてしまい、利用者はページ内容を閲覧できなくなります (Next.js, cache, and chains: the stale elixir - zhero_web_security)。攻撃者が定期的にキャッシュを汚染し続ければ、該当ページは事実上ダウンした状態になります。
- ストアドXSS: SSRページがユーザのリクエスト情報(User-Agentやクッキー値など)をページに埋め込んでいる場合、攻撃者はその部分にスクリプトを仕込んでキャッシュさせることで、他の利用者に対し悪意あるスクリプトを含んだHTMLを配信できます (Next.js, cache, and chains: the stale elixir - zhero_web_security) (Next.js, cache, and chains: the stale elixir - zhero_web_security)。一度キャッシュに載ってしまえば、次にそのページにアクセスしたユーザのブラウザ上でスクリプトが実行されるため、典型的なStored XSS(永続的クロスサイトスクリプティング)となります。
- 機密情報の漏洩: SSRページがログインユーザ固有のデータを表示する場合、キャッシュ汚染によってあるユーザのデータが他のユーザに表示されてしまう恐れもあります。攻撃者自身が閲覧権限のない情報を引き出すことは難しいものの、例えば管理者が閲覧したページを攻撃者が汚染しキャッシュすると、不特定多数にその内容が漏洩する可能性があります。
- その他の副次被害: キャッシュ汚染によりHTTPステータスコードまでも汚染されるケースも報告されています。実際に、攻撃リクエストで内部エラー(500)を発生させそれがキャッシュされてしまい、以降そのページがずっと500エラーを返すといった現象も確認されています (Is it safe to stripe off header 'X-Now-Route-Matches' before forwarding request to NextJs : r/nextjs)。
以上のように、攻撃難易度は低く(HTTPリクエストを送るだけ)かつ結果は深刻であるため、CVSS基本値は7.5(高深刻度)に分類されています (CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js)。幸い条件を満たす環境は限定されていますが、該当バージョンを使用しているサイトにとっては重大なリスクとなります。
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)。このヘッダが付与されたこと自体はNext.js内部の問題ですが、それだけでは即座に他のユーザへの被害は起きません。実際、攻撃者本人がそのレスポンス(JSONデータ)を受け取るだけなら被害は限定的です。
しかし問題は、そのレスポンスがキャッシュチェーン上でどのように扱われるかにあります。典型的にはNext.jsサーバの前段にあるCDNなどの共有キャッシュがそれを保存し、後続の通常リクエストに対して提供してしまうことが被害の引き金となります (Next.js, cache, and chains: the stale elixir - zhero_web_security)。すなわち、**「本来キャッシュキーが異なるはずのリクエストAとBが同一視され、攻撃者Bの応答が無関係なAに返る」**という状況が生まれるのです (Next.js, cache, and chains: the stale elixir - zhero_web_security)。
今回のケースでは、__nextDataReq=1
というパラメータ付きリクエストと、通常のリクエストでURLが異なります。しかしCDNの設定によってはクエリパラメータを無視してキャッシュすることがあります。その場合、攻撃者のJSON応答が通常ページのキャッシュとして蓄積され、以降パラメータ無しでアクセスしてきたユーザに対してJSONが返されてしまいました (Next.js, cache, and chains: the stale elixir - zhero_web_security)。さらにContent-Typeまでも誤った状態(HTMLとして扱われる)で提供されていたため、前述のXSSのような深刻な副作用も生じました (Next.js, cache, and chains: the stale elixir - zhero_web_security) (Next.js, cache, and chains: the stale elixir - zhero_web_security)。
加えて、Next.jsが複数の内部ヘッダを持っている点も関係します。今回悪用されたx-now-route-matches
以外にも、x-invoke-status
等の内部ヘッダが存在し、過去の調査ではこれらが応答内容(ステータスコードやエラーページ)に影響を与えることが指摘されています (Next.js, cache, and chains: the stale elixir - zhero_web_security)。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) (Next.js, cache, and chains: the stale elixir - zhero_web_security)。このように、Next.js内部のキャッシュ判断ロジックが外部から影響を受けたことが根本原因であり、キャッシュ機構の設計上の想定外利用と言えます。
エクスプロイト事例と PoC
CVE-2024-46982に対しては、セキュリティ研究者による詳細な調査とPoC(概念実証コード)が公開されています。GitHub上にはこの脆弱性を検出・悪用する自動化ツールも登場しており、具体的な攻撃手順が示されています (GitHub - Lercas/CVE-2024-46982: POC CVE-2024-46982)。その内容によれば、本脆弱性を悪用することで**「Next.jsのSSRレスポンスをSSGとして誤キャッシュさせ、Stored XSSやDoS、データ漏洩を引き起こす」**ことが可能だと説明されています (GitHub - Lercas/CVE-2024-46982: POC CVE-2024-46982)。
代表的な攻撃手順は以下の通りです (Next.js, cache, and chains: the stale elixir - zhero_web_security) (Next.js, cache, and chains: the stale elixir - zhero_web_security):
- 標的のSSRページエンドポイント(例:
/poc
)に対し、クエリパラメータ__nextDataReq=1
を付与する。これによりサーバはページをJSON形式で返すモードになる。 - 同じリクエストに内部ヘッダ
x-now-route-matches: 1
を付けて送信する。これでNext.jsはそのリクエストをSSG扱いし、キャッシュ制御ヘッダを付与したJSONレスポンスを返す。 - 攻撃者は上記リクエストをタイミングよく(キャッシュ有効期間が切れた直後など)送り、CDNに悪意あるJSONレスポンスをキャッシュさせる。必要ならスクリプトで連続リクエストし、この状態を安定して維持する (Next.js, cache, and chains: the stale elixir - zhero_web_security)。
- 他のユーザが通常のブラウザで対象ページ(
/poc
)にアクセスすると、キャッシュされた不正レスポンスが返却される。ユーザにはページ内容が表示されずDoS状態となったり、埋め込まれたスクリプトが実行されてXSSとなったりする (Next.js, cache, and chains: the stale elixir - zhero_web_security) (Next.js, cache, and chains: the stale elixir - zhero_web_security)。
実際に、この脆弱性は多くのサイトで再現可能であったと報告されています。とくに「独自にCDNを設定しており、URLパラメータをキャッシュキーに含めていない」「Vercel以外にホスティングしている」ケースで軒並み脆弱だったとのことです (Next.js, cache, and chains: the stale elixir - zhero_web_security)。研究者は複数のバグバウンティプログラムで本脆弱性を報告し、報奨金を得たことも明らかにしています (Next.js and cache poisoning: a quest for the black hole - zhero_web_security) (Next.js and cache poisoning: a quest for the black hole - zhero_web_security)。
以上の事例から、攻撃の現実性は非常に高いと言えます。公開されたPoCツールでは、User-AgentヘッダにXSSペイロードを含めてキャッシュ汚染し、実際に他ユーザでアラートを表示させるといった攻撃が自動化されています (GitHub - Lercas/CVE-2024-46982: POC CVE-2024-46982) (GitHub - Lercas/CVE-2024-46982: POC CVE-2024-46982)。今後もパッチ未適用のサイトに対しては、同様の攻撃が行われる可能性があるため注意が必要です。
修正方法と対策
Next.js開発チームは本脆弱性を認識し、2024年9月18日付けで修正版をリリースしました (Next.js Cache Poisoning · CVE-2024-46982 · GitHub Advisory Database · GitHub) (Next.js Cache Poisoning · CVE-2024-46982 · GitHub Advisory Database · GitHub)。以下のバージョンで問題が修正されています (CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js):
- Next.js 13 系: v13.5.7
- Next.js 14 系: v14.2.10
そのため、最も確実で推奨される対策は該当バージョン以降へのアップグレードです (CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js)。Next.js公式も「問題を再現できるかに関わらず、すみやかに安全なバージョンへ更新すること」を強く推奨しています (NVD - CVE-2024-46982)。
また、Next.jsから公式に案内されているワークアラウンド(回避策)は存在しません】 (NVD - CVE-2024-46982)。唯一の根本対処はアップデートとなります。とはいえ、アップデートがすぐに適用できない場合の一時的な緩和策**として、以下のような対処が考えられます:
-
リバースプロキシ/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)。もし一部の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))も踏まえ、自社環境で追加すべき防御策がないか検討してください。
関連する脆弱性と今後の対応
類似の既知脆弱性
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)。具体的には、x-middleware-prefetch
というヘッダを付けてSSRページのデータを事前取得しようとすると、サーバは内容の無い空のJSON {}
を返します (Next.js and cache poisoning: a quest for the black hole - zhero_web_security)。問題はこの応答にCache-Controlヘッダが適切に付与されていなかったため、CDNによってはその空のレスポンスがキャッシュされてしまい、結果として本来のページコンテンツが表示されなくなるというものでした (Next.js and cache poisoning: a quest for the black hole - zhero_web_security) (Next.js and cache poisoning: a quest for the black hole - zhero_web_security)。この欠陥もまたキャッシュポイズニングによるDoSの一種であり、CVSS7.5に評価されています (Next.js and cache poisoning: a quest for the black hole - zhero_web_security)。Next.jsチームはこの問題に対し、SSRレスポンスにCache-Controlヘッダを付与する変更を行い(v13.4.20-canary.13で修正 (Next.js and cache poisoning: a quest for the black hole - zhero_web_security))、同様のプレフェッチによるキャッシュ汚染を防止しました。
また、今回のCVE-2024-46982を報告した研究者は、Next.jsにおける複数のキャッシュ関連問題を調査しており、その中でReact Server Components (RSC)とCDNの関係にも触れています (Next.js and cache poisoning: a quest for the black hole - zhero_web_security)。RSCリクエストではRsc: 1
ヘッダが用いられ、Next.jsはこれに基づきVary
ヘッダを設定していましたが、一部CDNがこれを正しく扱わずRSCペイロードをHTMLページとしてキャッシュする事態が起きていました (Next.js and cache poisoning: a quest for the black hole - zhero_web_security) (Next.js and cache poisoning: a quest for the black hole - zhero_web_security)。Next.js側はこれに対し、先述のとおり内部的に_rsc
というキャッシュバスター用のクエリを導入する対応を取っています (Next.js and cache poisoning: a quest for the black hole - zhero_web_security)。このように、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)。AppルーターではVary
ヘッダの適切な使用など近代的なキャッシュ制御が導入されていますが、今後はより明示的なキャッシュ制御がなされる可能性があります。例えば、今回のように誤って静的扱いされた場合でもmax-age=0
を強制する保険的なチェックを入れる、CDNに対する注意喚起をドキュメントに追記する、といった対応が考えられます。
公式情報としては、GitHubのセキュリティアドバイザリやリリースノートに修正内容が記載されています (Next.js Cache Poisoning · CVE-2024-46982 · GitHub Advisory Database · GitHub)。そこでは該当コードの修正(commit)へのリンクも提示されており、実際にどのように問題が修正されたか追うことができます (Next.js Security Update Advisory (CVE-2024-46982) - ASEC)。コミット履歴によれば、問題の原因となった不適切な再検証値(revalidate
のデフォルトが1になっていた等)を除去し、内部ヘッダの扱いを改善する変更が含まれています (Remove invalid fallback revalidate value (#69990) · vercel/next.js@7ed7f12 · GitHub) (Remove invalid fallback revalidate value (#69990) · vercel/next.js@7ed7f12 · GitHub)。
最後に、開発者・利用者への注意喚起として**「最新の安全なバージョンへの更新の徹底」**が挙げられます。Next.js公式ブログやコミュニティでも、依存パッケージの定期的なアップデートやセキュリティ情報のチェックが推奨されています (CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js) (CVE-2024-46982: Understanding and Mitigating Cache Poisoning in Next.js)。フレームワーク自体の脆弱性はアプリケーション全体に影響するため、今後もNext.jsに限らず依存ソフトウェアのアップデートを怠らないことが重要です。
以上、Next.jsのキャッシュ機構とCVE-2024-46982の詳細について技術的に解説しました。本脆弱性はキャッシュ戦略の盲点を突いたものでしたが、適切な対策を講じることで被害を防ぐことができます。開発者はキャッシュの挙動を正しく理解し、フレームワークのアップデートと設定確認を通じて、安全なWebアプリケーション運用を心がけてください。