React RCE脆弱性(CVE-2025-55182) – 問題と発生原因

2025年12月初旬に公開されたReact Server Components脆弱性(CVE-2025-55182、別名 React2Shell / React4Shell)は CVSS 10.0 が付与された、認証不要で リモートコード実行(RCE)が可能な非常に深刻な問題です。公開PoCと実際のスキャン・攻撃試行が観測されています。

この記事は 「誰が、なぜ、どこまで危険か」 をより体系的に整理したバージョンです。


一目でわかるまとめ



  • 問題の核心 React Server Components(RSC)が使用する Flightプロトコルの逆シリアライズロジックの欠陥 により、攻撃者が改ざんしたリクエストを送るとサーバーで任意のJSコードが実行される可能性があります。

  • 影響を受ける主要構成要素

  • react-server-dom-webpack / -parcel / -turbopack 19.0 / 19.1.0 / 19.1.1 / 19.2.0

  • これらを内蔵した Next.js 15.x, 16.x, 14.3.0-canary.77以降バージョン(App Router使用時)

  • ブラウザのみで動く「従来型React SPA」 RSC/Server Actions/サーバーサイドレンダリングを一切使用しない純粋クライアントReactアプリは影響なし。

  • 読者の中で過去にNext.jsを使用したことがある方はすぐに行うべきこと

  1. プロジェクトで react-server-dom-* / next のバージョンを確認
  2. Reactは 19.0.1 / 19.1.2 / 19.2.1 以上に更新
  3. Next.jsは公表されたパッチバージョン(15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7 など)へ更新、または安定版へダウングレード
  4. 既にインターネットに公開されているサーバーなら、ログ・WAFルール・異常兆候を併せて確認

image

今回発見された脆弱性とは

Reactチームが公開した内容だけを整理すると、今回の脆弱性は次のようにまとめられます。

  • 脆弱対象: React Server Components(RSC)実装体(react-server-dom-*
  • 脆弱箇所: クライアント→サーバーへRSCデータを送信する際に使用する Flightプロトコルのデコード(逆シリアライズ)ロジック
  • 攻撃手法:

  • 攻撃者が 特別に改ざんされたFlightペイロード を RSC/Server Actions HTTPエンドポイントへ送信

  • サーバーがこのデータを「信頼」し逆シリアライズする過程で、内部モジュールロード/オブジェクト生成ロジックが攻撃者入力に引きずられる
  • その結果、 サーバー環境で任意コード実行(RCE) が可能

重要なのは、ログイン/セッションなし(Unauthenticated) で攻撃が可能である点です。外部に公開されたNext.js RSCサーバーなら「インターネットに開放された瞬間から」スキャン・攻撃対象になります。


脆弱性が発生する正確な条件



1. React/Next.js バージョン条件

  • React RSC関連パッケージ

  • react-server-dom-webpack

  • react-server-dom-parcel
  • react-server-dom-turbopack

  • 脆弱バージョン

  • 19.0, 19.1.0, 19.1.1, 19.2.0

  • パッチバージョン

  • 19.0.1, 19.1.2, 19.2.1

  • Next.js(App Routerベース)

  • 脆弱:

    • 14.3.0-canary.77以上
    • 15.x, 16.x(RSCを含む基本App Router構造)
    • パッチ:

    • 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7 など

2. アーキテクチャ/ランタイム条件

以下の条件が すべて 当てはまると実際に攻撃が可能性が高いです。

  1. RSC使用 * Next.js App Router、または他のRSCサポートフレームワーク使用

  2. サーバーでReactコード実行 * Node.js等でReact Server Components/Server Actionsが動作する構造

  3. HTTPで外部に公開されたサーバー * インターネットまたは内部ネットワークであっても、攻撃者がHTTPリクエストを送れる環境

逆に、以下の環境は今回の脆弱性とほぼ無関係です。

  • CRA/Viteベースの 純粋クライアントReact SPA
  • RSC/Server Actionsを一切使わず、単に静的バンドルをS3、CDN等にアップロードする方式
  • 「Reactはブラウザのみで実行され、サーバーは完全に別の技術スタック(例: React + Django REST, React + FastAPI)」

React-DRF、React-FastAPI組み合わせはなぜ相対的に安全だったか

ReactをDjango REST FrameworkやFastAPIなどのPythonバックエンドと組み合わせて使用する構造では問題が発生しません。理由は次のとおりです。

  1. Reactコードがサーバーで実行されない
  • Reactはブラウザでのみ動き、Django/FastAPIは純粋なHTTP APIサーバーとして機能
  • サーバーはReactのFlightプロトコルを理解する必要も、RSCを解釈する必要もない
  1. 通信境界が明確
  • React ↔ サーバー間の通信はJSON/HTTP APIで明確に区別され、
  • サーバーは「React内部モジュール」を require() したり、クライアントが渡した metadata でモジュールをロードしたりしない
  1. RSC・Server Actions・Flightプロトコルを使用しない
  • 今回の脆弱性の攻撃表面は「RSC Flightペイロードを解釈するサーバー側Reactコード」にある
  • Django/FastAPIはそのコードを全く含まない

結局、今回の事故は 「React + サーバー統合フレームワーク(Next.js等)でのみ発生可能な構造的問題」 でした。役割が分離された React-SPA + REST API 組み合わせは、当初から問題となるコードパスが存在しませんでした。


Flightプロトコルとサーバー側プロトタイプ汚染

もう少し技術的に掘り下げると、脆弱性は Flightプロトコル逆シリアライズ → プロトタイプ汚染 → RCE という典型的な流れを持ちます。

1. 問題となったコードパターン

問題となった関数の概念的な形は次のとおりです(概念化した例)。

// 脆弱バージョン(概念コード)
export function requireModule<T>(metadata: ClientReference<T>): T {
  const moduleExports = parcelRequire(metadata[ID]); // モジュールロード
  return moduleExports[metadata[NAME]];              // クライアント入力をそのまま使用
}
  • metadata[ID]: どのモジュールをロードするか
  • metadata[NAME]: そのモジュールのどのexportにアクセスするか

ここで metadata[NAME] が全く検証されずにそのまま使用 されている点が問題です。攻撃者がこの値を __proto__constructor などの敏感なキーに変更すると、モジュールexportではなく オブジェクトプロトタイプチェーンに手を入れる道 が開かれます。

2. プロトタイプ汚染 → RCE

  • サーバーコードのどこかで「普通のオブジェクト生成」を行うと、
  • 既に汚染された Object.prototype の属性が付随します。
  • その中に spawnSync('sh') などのシェル実行ロジックが接続されていれば、 新しく生成されるオブジェクトを経由してコードが実行 されます。
  • こうして攻撃者は 1回のHTTPリクエストでリモートコード実行 に到達できます。

実際に何をパッチしたのか

React側のパッチの核心は 「クライアントが送った値をそのまま信頼しない」 ことを強制する点です。

代表的な変更は requireModule 関数です。

// パッチ後(概念コード)
export function requireModule<T>(metadata: ClientReference<T>): T {
  const moduleExports = parcelRequire(metadata[ID]);

  // 👇 クライアントが送ったNAMEが実際own propertyか確認
  if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
    return moduleExports[metadata[NAME]];
  }

  return undefined as any;
}

この1行で:

  • __proto__constructor などプロトタイプチェーンを触るキーは own propertyではないため アクセスが遮断されます。
  • その結果、プロトタイプ汚染をRCEに結びつける主要攻撃ベクター が遮断されます。

実際のパッチにはこれ以外にも:

  • Flightペイロード構造検証の強化
  • 特定の危険なフィールドの使用制限
  • 関連コードパスへの追加防御ロジック

などが含まれていますが、全体像としては上記の「検証追加」が核心です。


Next.jsがReactをサーバーへ拡張しながら生じた構造的衝突

元々Reactは「ブラウザで動くUIライブラリ」でした。 しかしRSCとServer Actionsが登場すると、Reactはもはやブラウザ専用ではなくなります。

Next.jsはこの機能を統合して:

  • クライアントコンポーネント + サーバーコンポーネント + サーバーアクション を1つのアプリとして見せます。
  • その代償として、クライアントが送るデータがサーバーモジュールロード/コード実行パスを直接触れる構造 が生まれました。

ここでセキュリティモデルがずれました。

フロントエンドの仮定をサーバーに持ち込んだ問題

フロントエンド環境では通常:

  • コードバンドルは 静的で不変 で、
  • モジュールロードは ビルド時に決定 され、
  • ランタイムでユーザーがモジュール名やexport名を変えることはほぼありません。

したがって、

moduleExports[userInput]

というパターンは相対的にリスクが低いと感じられます。

しかしサーバーでは話が全く異なります。

  • 入力は常に 信頼できないネットワークデータ であり、
  • モジュールロード/オブジェクト生成は サーバーリソース・ファイル・プロセス への直結です。
  • こうした環境で逆シリアライズ/ダイナミックロードにユーザー入力を混ぜると、常にRCEの候補 になります。

必要だったのは次のような「新しいセキュリティモデル」でした。

  • Flightペイロード構造に対する 厳格なスキーマ検証
  • クライアントが制御可能な値に対する ホワイトリストベースアクセス
  • HMACなどの リクエスト署名/整合性検証
  • サーバー側で「RSCは常に信頼できない入力を受け取る」前提で設計

しかし実装では、従来のReact(フロント専用)信頼モデル がサーバー領域までそのまま持ち込まれ、今回のような構造的脆弱性が露呈しました。


結論:フロントエンドとサーバー境界が曖昧になるほど、セキュリティモデルを再設計すべき

今回のReact/Next.js脆弱性は単なる「バグ一つ」ではなく:

  • フロントエンドライブラリだったReactがサーバー実行パスまで包括する瞬間
  • 既存の「フロントエンド中心信頼モデル」が通用しなくなったことを示しています。

従来の React + DRF/FastAPI のように:

  • 「ブラウザでのみReactが動き」
  • 「サーバーはHTTP APIのみ提供」 というアーキテクチャでは今回のような脆弱性が存在しません。

逆に、Next.jsのように:

  • サーバー実行パスにReactのシリアライズ/逆シリアライズプロトコルが深く絡む構造では、
  • 機能とDXの向上と同時に 新しいセキュリティモデルと検証体系 を設計する必要があります。

今後も「フロントエンド-バックエンド境界が消える」フレームワークは続くでしょう。 今回の事件は、便利さと同等に、セキュリティ境界と信頼モデルを最優先で設計すべき という警告と捉えることができます。