技術 約10分で読めます

Next.jsのWebSocketアップグレードSSRFはセルフホストだけが踏むCVE-2026-44578

いけさん目次

TL;DR

影響 Next.js 13.4.13以上15.5.16未満、16.0.0以上16.2.5未満を組み込みNode.jsサーバーで自前運用している環境。CVSS 8.6 HIGH、Vercel上のNext.jsは対象外

対応 Next.js 15.5.16または16.2.5以上へ更新。2026年5月7日の累積セキュリティ更新と合わせるなら15.5.18または16.2.6

暫定 リバースプロキシで「絶対形式リクエストターゲット + Upgrade: websocket」の組み合わせを遮断。クラウドメタデータエンドポイントとRFC 1918への外向き通信も併せて絞る

検出 Next.jsプロセスから169.254.169.254、metadata.google.internal、RFC 1918、リンクローカルへの予期しないHTTP GET。アップグレードハンドラ経由のログは通常HTTPアクセスログと別系統で確認


Next.jsのCVE-2026-44578は、Vercel上のNext.jsではなく、組み込みNode.jsサーバーをそのままセルフホストしている環境で踏むSSRFだ。
SSRFはServer-Side Request Forgeryの略で、外部から来たリクエストをきっかけに、サーバー自身へ内部ネットワーク宛てのHTTPリクエストを投げさせる攻撃を指す。

GitHub AdvisoryのGHSA-c4j6-fc7j-m34rでは、影響範囲はNext.js 13.4.13以上15.5.16未満、16.0.0以上16.2.5未満。
修正版は15.5.16と16.2.5で、CVSSは8.6。
2026年5月7日のNext.js 16.2.6 / 15.5.18のセキュリティ更新にも同じadvisoryが含まれているので、今から上げるなら15.5.18または16.2.6まで行くほうが楽だ。

経路はHTTPではなくアップグレードハンドラ

今回の入口は普通のページアクセスではなく、HTTP/1.1のWebSocketアップグレードリクエストだ。
クライアントがConnection: UpgradeUpgrade: websocketを付けてNext.jsへ接続すると、Node.js側では通常のリクエストハンドラではなくアップグレードハンドラが動く。

問題は、このアップグレードハンドラが外部URLっぽいリクエストターゲットを見たときの扱いにあった。
GitHub Advisoryの説明では、通常のHTTPリクエスト側には入っていた安全確認が、WebSocketアップグレード側にも適用されるよう修正された。
つまり「ルーティングが安全な外部リライトとして明示した場合だけプロキシする」という条件が、アップグレードリクエストにも揃えられた。

Hadrianの技術分析では、絶対形式のリクエストラインとアップグレードヘッダーを組み合わせると、Next.jsプロセスから攻撃者指定のホストへHTTP GETが飛ぶ流れとして整理されている。
到達先は実質的にポート80へ寄る。
そのため、クラウドメタデータエンドポイント、内部管理画面、社内APIのHTTP口がNext.jsサーバーから見える構成だと、外から直接触れないはずの情報が返る。

実際の攻撃リクエストは概ね次の形になる。
通常のWebSocketクライアントは相対形式(GET /chat HTTP/1.1)でリクエストを送るが、絶対形式(GET http://...)はHTTP/1.1仕様上プロキシ向けに残されている記法だ。

GET http://169.254.169.254/latest/meta-data/iam/security-credentials/ HTTP/1.1
Host: app.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

通常のNext.jsはRouterのリライト設定で許可された外部URLのみプロキシするはずだが、修正前のアップグレードハンドラはこの安全確認をすり抜けていた。
結果として、Next.jsプロセスが自分自身を踏み台にして、絶対形式で指定されたホストへHTTP GETを投げる。

flowchart TD
    A["攻撃者"] -->|absolute-form + Upgrade: websocket| B["Next.js組み込みNode.jsサーバー"]
    B --> C["アップグレードハンドラ"]
    C -->|安全確認をスキップ| D["absolute-formのターゲットへ<br/>HTTP GETを発行"]
    D --> E["169.254.169.254<br/>クラウドメタデータ"]
    D --> F["RFC 1918<br/>社内HTTP API・admin UI"]
    D --> G["リンクローカル<br/>k8s API・etcd・Redis"]
    E --> H["IAMクレデンシャル等が<br/>レスポンスとして返る"]

SSRFで狙われる内部エンドポイント

SSRFが成立したとき、Next.jsプロセスから到達可能な「外から直接見えないが、内部からなら認証なしで叩ける」エンドポイントが攻撃価値を決める。
クラウド環境とコンテナ環境では特に以下が常連だ。

経路アドレス例取れるもの
AWS IMDSv1169.254.169.254/latest/meta-data/EC2ロールの一時クレデンシャル、リージョン情報
AWS IMDSv2同上PUTでセッショントークン取得が要るので、単純なGET SSRFでは入りにくい
GCP metadatametadata.google.internal/computeMetadata/v1/サービスアカウントトークン(Metadata-Flavor: Googleヘッダー要)
Azure IMDS169.254.169.254/metadata/Managed Identityトークン(Metadata: trueヘッダー要)
Kubernetes APIクラスタIP、kubernetes.default.svcPod内ServiceAccountトークンがある場合は管理操作
Docker socket TCP:2375任意コンテナ起動・ホスト侵害
Redis:6379認証なしならキー読み出し・CONFIG SETでRCEルート
etcd:2379クラスタの構成・秘密情報
内部admin UIRFC 1918のHTTPポート認証回避できればそのまま設定変更

ヘッダーが追加で必要なGCP・Azureメタデータ、PUTが必要なIMDSv2は、HTTP GET単発のSSRFでは到達できない場合が多い。
ただ、それでもAWS IMDSv1や認証なしの内部エンドポイント、Redisのテキストプロトコル経由のRCEルートは現実的に成立する。
Next.jsサーバーがどのプライベートネットワーク・リンクローカル範囲へ出られるかを、まずアウトバウンドファイアウォール側で見ておく。

Vercel上のNext.jsは対象外

advisoryは、Vercel上のデプロイは影響を受けないと明記している。
条件に入るのは、Docker、VM、KubernetesなどでNext.jsの組み込みNode.jsサーバーを動かし、そこへ未信頼ネットワークからWebSocketアップグレードリクエストが届く構成だ。

ここは以前のReact2Shell対応メモとは踏み方が違う。
React2ShellはRSCのデシリアライズ処理が中心で、App RouterやReact 19系の利用状況を見る話だった。
CVE-2026-44578は、Next.jsサーバーのHTTPアップグレード経路とセルフホスト構成を見る話になる。

静的サイトをNext.jsからAstroへ移した話を別記事で書いたが、今回も差はそこに出る。
静的HTMLだけをCDNから配るサイトなら、このWebSocketアップグレードハンドラ自体が本番の攻撃面に出ない。
一方で、SSR、Route Handler、WebSocket系の機能を同じオリジンサーバーで受けているNext.jsは、フレームワーク更新だけでなく、オリジンをどう公開しているかまで確認がいる。

すぐ更新できないオリジンで止める場所

修正はNext.jsを上げることだ。
15系なら15.5.16以上、16系なら16.2.5以上。
既に5月の一括セキュリティ更新を追うなら、15.5.18または16.2.6へ上げる。

pnpm up next@16.2.6

15系に残すならこうする。

pnpm up next@15.5.18

すぐ上げられないオリジンでは、Next.jsサーバーをインターネットへ直接出さない。
リバースプロキシやロードバランサーの前段で、絶対形式のリクエストターゲットを落とす。
WebSocketをアプリで使っていないならアップグレードリクエスト自体を止める。
使っているならUpgrade: websocketを丸ごと落とすのではなく、http://https://で始まるリクエストターゲットと組み合わさったものを弾く。

nginxを前段に置く構成なら、リクエストURIをチェックして落とせる。

# /etc/nginx/conf.d/nextjs.conf
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 443 ssl http2;
  server_name app.example.com;

  # absolute-form リクエストラインは破棄
  if ($request_uri ~* "^https?://") {
    return 400;
  }

  location / {
    proxy_pass http://nextjs_upstream;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;
  }
}

Caddyならリクエストパスのマッチで弾ける。
CloudflareなどCDN前段でも、URLが http:// / https:// で始まる場合のリクエスト破棄ルールを書く。

クラウド上ではメタデータサービスへの外向き通信も絞る。
AWSならIMDSv2必須化がこの経路の被害をかなり狭める(PUTメソッドでセッショントークンを取得しないと情報を引けないため、単発GETのSSRFでは到達しない)。
GCPは Metadata-Flavor: Google ヘッダーを要求するので、ヘッダーを攻撃者が制御できないSSRFでは同様に止まる。
ただし、内部のポート80で管理画面や古いHTTP APIが見えている場合はメタデータだけ塞いでも残る。
Next.jsプロセスからどのプライベートIP・リンクローカル・Service IP範囲へ出られるかを、アウトバウンド側のNetworkPolicyやSecurity Groupで見るほうが早い。

ログに残りにくいアップグレードリクエスト

WebSocketアップグレードは通常のHTTPリクエストと同じログに残らない構成がある。
リバースプロキシのアクセスログだけ見て「該当なし」と判断すると漏れる。

確認したい通信パターンは以下に整理できる。

  • リクエストライン側: 先頭が GET http:// または GET https:// で、Upgrade: websocket ヘッダーが同時に付いた通信
  • Next.jsプロセスのアウトバウンド: 169.254.169.254:80metadata.google.internal:80、RFC 1918(10.0.0.0/8172.16.0.0/12192.168.0.0/16)、リンクローカル(169.254.0.0/16fe80::/10)への急な接続
  • ServiceMesh/k8s経路: クラスタ内Service IPへの外部由来トラフィック、特にコントロールプレーンへの異常な接続

nginxのアクセスログから絶対形式のリクエストを引くなら、以下のような形になる。

# 絶対形式リクエストラインを抽出
zgrep -E '"(GET|POST|HEAD) https?://' /var/log/nginx/access.log* \
  | grep -i 'upgrade.*websocket'

Next.jsアプリ側のログでも、http-proxy 経由のエラー、アップグレード処理の失敗、内部URLへの予期しないfetchをキーに見る。
コンテナ環境なら、netstat / ss ベースの定期収集や、ciliumなどのeBPFベースNetworkPolicyでアウトバウンド先のホワイトリストから外れた接続を検出する。

このCVEはRCEではない。
それでも、サーバーに付いたIAM roleや内部APIの権限が強いと、HTTP GETだけで十分に痛い情報が返る。
Next.jsのバージョンを上げたあと、オリジン直公開とメタデータ到達性を潰しておく。

Next.jsの脆弱性は短期間で重なっている

2025年末から2026年5月までの半年で、Next.jsには重大なアドバイザリが続いた。

  • 2025年12月: React2Shell(RSCデシリアライズ) — App RouterとReact 19系の特定構成で発火する権限関連の脆弱性。詳細はReact2Shell対応メモ
  • 2026年4月: UAT-10608がReact2Shellを実戦投入 — 公開後の脆弱性を使った認証情報窃取キャンペーン
  • 2026年5月7日: 累積セキュリティ更新16.2.6 / 15.5.18 — 複数アドバイザリをまとめた一括更新
  • 2026年5月14日: WebSocketアップグレードSSRF(本記事)

共通するのは、フレームワーク内部の処理経路が外部入力をどう扱うかという設計面の問題が多いことだ。
RSCのデシリアライズ、認証ロジックの順序、HTTPアップグレード処理 — いずれもアプリケーションコードの外側で動く層が含まれる。

セルフホストでNext.jsを動かすなら、フレームワーク本体のセキュリティリリースを継続的に追う体制がいる。
オリジン直公開を避け、リバースプロキシまたはCDNで前段を挟む構成は、フレームワーク側パッチの遅れを吸収する保険になる。
SSRオリジンが系全体の最遅APIになりがちな性質も含めて、フルセルフホスト構成は運用コストが高めだ。

代替路線としては、Next.jsをCloudflare向けVite基盤に寄せるVinext、Next.js上に乗るフルスタックツールキットBlitz.jsあたりが選択肢に入る。
ただし、移行は別の長期判断で、当面はNext.js本体の更新追従が最優先だ。

参考