CVE-2025-55182 は、ReactServer に存在する、認証前のリモートコード実行が可能な重大な脆弱性であり、CVSS スコアは 10.0(最高レベルの深刻度)となっています。OPSWAT ・プログラムの一環として、当社のフェローはこの脆弱性について包括的な技術分析を実施し、React Flightのデシリアライゼーションプロトコルにおける根本原因、完全なエクスプロイトチェーン、および現代のWebエコシステム全体に及ぼす広範な影響を調査しました。本ブログでは、調査結果と併せて、防御担当者向けの具体的な対策指針をご紹介します。

Reactは、世界で最も広く採用されているフロントエンドライブラリの一つとなり、現代のWebおよびmobile の相当な割合を支えています。 Stack Overflowの開発者調査では、Reactは常にトップクラスのWebフレームワークとしてランクインしており、世界中のプロフェッショナル開発者の40%以上が採用しています。この成長に伴い、ReactチームはReact 19の中核機能としてReactServer (RSC)を導入しました。これは、レンダリングロジックをクライアント側からサーバー側へ移行させるパラダイムシフトであり、パフォーマンスの最適化と、サーバーサイドコードとクライアントサイドコードのより緊密な統合を実現します。
しかし、このアーキテクチャの進化により、重大な新たな攻撃対象領域が生まれました。2025年11月29日、セキュリティ研究者のラクラン・デビッドソン氏が、Reactのサーバーサイド逆シリアライズロジックに存在する脆弱性をMetaのバグ報奨金プログラムに報告しました。2025年12月3日にCVE-2025-55182として公開されたこの脆弱性は、1つの細工されたHTTPリクエストを通じて、認証不要のリモートコード実行を可能にするものです。 CWE-502(信頼できないデータのデシリアライズ)に分類されるこの脆弱性は、認証もユーザーの操作も特別なアプリケーション設定も必要とせず、本番環境向けに構築されたデフォルトの「create-next-app」デプロイメントであれば、即座に悪用可能です。

その影響は即座に、かつ深刻なものとなりました。公開から48時間以内に、実環境において複数の悪用キャンペーンが確認されました。The Shadowserver Foundationによると、77,000件以上のパブリックIPアドレスが、脆弱性の影響を受ける可能性があると特定されました。 Cloudflareのテレメトリデータによると、公開後の1週間で5億8,200万件以上の攻撃試行が記録され、攻撃の強度は1時間あたり平均3,500以上のユニークな送信元IPに達し、同時攻撃IP数は最大16,585件を記録した。Wiz Researchの報告では、クラウド環境の39%に脆弱なインスタンスが存在していた。 CISAは2025年12月5日、この脆弱性を「既知の悪用済み脆弱性(KEV)」カタログに追加した。
攻撃者は、驚くべきスピードと多様性をもって活動しました。トレンドマイクロは、「emerald」や「nuts」といったボットネットキャンペーンを含む複数のキャンペーンを記録しており、これらではCobalt Strikeビーコン、Sliverインプラント、Nezhaモニタリングエージェント、Fast Reverse Proxy(FRP)トンネル、そしてTruffleHogやGitleaksといったオープンソースの認証情報収集ツールを悪用した新たな「Secret-Hunter」ペイロードが展開されていました。 GoogleThreat Intelligence 、MINOCATトンネラー、SNOWLIGHTダウンローダー、COMPOODバックドア、HISONICバックドアといった特殊なツールを展開する、中国関連の明確な脅威クラスター(UNC6600、UNC6586、UNC6588、UNC6603)を特定した。これらは、イラン関連のアクターや、仮想通貨マイニングキャンペーンを行う金銭的動機を持つグループと並行して活動していた。 AWSは、完全な概念実証(PoC)コードが一般に公開される前の12月4日時点で、中国関連のグループがエクスプロイトコードの実験を行っていたことを記録している。
ReactServer について
Reactは、Metaおよび広範なオープンソースコミュニティによってメンテナンスされている、ユーザーインターフェースを構築するためのJavaScriptライブラリです。 React 19で導入されたReactServer (RSC)は、Reactアプリケーションのレンダリング処理方法における根本的な変革を表しています。ブラウザ内で完全に実行される従来のクライアントコンポーネントとは異なり、サーバーコンポーネントはサーバー上で実行され、UIのシリアライズされた表現を生成してクライアントにストリーミングします。この設計により、ブラウザに送信されるJavaScriptの量が削減され、インタラクティブになるまでの時間が短縮されるほか、データベースやファイルシステムなどのサーバーサイドリソースへの直接アクセスが可能になります。

RSCは、クライアントとサーバー間でデータをエンコードおよび送信するために、「Flight」と呼ばれるカスタムシリアライゼーションプロトコルを採用しています。Server (旧称:Server )を呼び出すと、ブラウザはFlight形式を使用して関数の引数を構造化されたHTTPリクエストにパッケージ化します。 サーバーはこのペイロードをデシリアライズし、要求された関数を実行して、その結果をクライアントにストリーミングで返します。クライアントとサーバーのこの密接な結合(アーキテクチャ的には洗練されていますが)は、CVE-2025-55182が示すように、デシリアライズロジックに何らかの欠陥があると、即座に壊滅的な結果をもたらす可能性があることを意味します。
この脆弱性は、React自体だけでなく、Reactを基盤として構築されたフレームワークのエコシステム全体に影響を及ぼします。 Next.js(別途アドバイザリ CVE-2025-66478 が発行されたが、後に重複として却下された)、React Router、Waku、Parcel の RSC プラグイン、Vite の RSC プラグイン、および RedwoodSDK がすべて影響を受けます。フレームワークで RSC サポートが有効になっている場合、Server 定義していないアプリケーションであっても、脆弱性の影響を受ける可能性があります。
技術的背景
この脆弱性を検証する前に、エクスプロイトチェーンの基盤となる3つの概念について説明します。それは、JavaScriptにおける`thenable`オブジェクトとの`await`の動作、プロトタイプチェーンのトラバーサル、そしてReact Flightプロトコルのチャンクベースのデータモデルです。
JavaScriptの`await`と`Thenable`オブジェクト
`await` 演算子は、待機対象の式が解決されるまで非同期関数の実行を一時停止します。`await` がネイティブのPromise に遭遇した場合、その解決を待ち、解決された値を返します。ただし、`await` は必ずしもネイティブの Promise を必要とするわけではありません。「thenable」と呼ばれる`.then()`メソッドを持つ任意のオブジェクトは、Promise のような構造として扱われます。
`await` が `thenable` オブジェクトに遭遇すると、システムが提供する `resolve` および `reject` コールバックを渡して、そのオブジェクトの `.then()` メソッドを呼び出します。`resolve` に渡された値が、`await` 式の結果となります。 重要な点として、解決された値自体が Thenable である場合、そのネストされたオブジェクトの .then() メソッドが、プリミティブ値または解決済みの Promise に到達するまで再帰的に呼び出されます。この再帰的な解決動作は、CVE-2025-55182 の悪用において中心的な役割を果たします。
プロトタイプのチェーン走査
すべてのJavaScriptオブジェクトは、そのプロトタイプへの内部リンクを保持しており、これは__proto__プロパティを介してアクセスできます。オブジェクトのプロパティにアクセスすると、JavaScriptエンジンはまずそのオブジェクト自身のプロパティを確認します。プロパティが見つからない場合、エンジンはプロトタイプチェーンを辿り(各__proto__リンクを順に遡って)、プロパティが見つかるか、チェーンがundefinedで終了するまで処理を続けます。
攻撃者は、この継承メカニズムを悪用して、オブジェクトの意図されたスコープを超えたプロパティにアクセスすることが可能です。プロパティへのアクセスパスに `__proto__` を含めることで、攻撃者は、アプリケーションが公開する意図のなかった内部メソッドやコンストラクタに到達することができます。JavaScript では、式 `obj.__proto__.constructor.constructor` はグローバルな `Function` コンストラクタを返します。このコンストラクタは、文字列の入力から任意の関数を作成・実行することが可能です。
React Flight Protocol とチャンクベースのデータモデル
When a client invokes a Server Function, the browser sends an HTTP POST request with a multipart/form-data body. Each form field contains a numbered “chunk” of serialized data. The Flight protocol uses special string prefixes to encode data types: $<id> references the resolved value of another chunk, $@<id> references the raw chunk object itself, $W<id> represents a Set, $K<id> represents FormData, and $B<id> triggers the blob handler.
次のように定義されたServer 考えてみましょう:

対応するHTTPリクエストには複数のフォームフィールドが含まれており、各フィールドはキーと値で構成されています。フィールド0には「$W1」や「$K2」といった参照を含む引数配列が格納され、フィールド1および2_*には、それらの参照が解決されたデータが格納されます。サーバーは各フィールドが到着するたびにそれを処理し、中間結果を「チャンク」と呼ばれるオブジェクトに格納します。

チャンクは、4つの主要なプロパティ(status(解決状態)、value(保存されたデータ)、reason(エラー情報)、_response(親のレスポンスオブジェクトへのバックリファレンス))を持つThenable オブジェクトです。 サーバーが `await chunk` に遭遇すると、チャンクの`.then()` メソッドが呼び出されます。チャンクのステータスが `INITIALIZED` の場合、解決コールバックは `chunk.value` を受け取ります。ステータスが `PENDING`、`BLOCKED`、または `CYCLIC` の場合、コールバックは後続の実行のためにキューに入れられます。

chunk_0 は通常、呼び出されたServer 引数配列を表します。すべてのフォームフィールドの受信と内部参照の解決が完了すると、chunk_0.value には完全に組み立てられた引数配列が含まれ、それがターゲット関数に渡されます。
エンドツーエンドのリクエスト処理(Next.js → React Flightのデシリアライズ)
以下では、通常の条件下でNext.jsが受信したServer リクエストを、HTTPレイヤーからReact Flightのデシリアライズエンジンに至るまで、どのように処理するかを解説します。

関数 handleAction() - Next.js
Server 呼び出されると、リクエストは `handleAction` 関数に渡されます。この関数では、メタデータの検証、ヘッダーとCSRFトークンのチェックを行い、リクエストが有効なフェッチアクションであることを確認します。 その後、busboyStreamという名前のストリームが作成され、マルチパートのフォーム本文が解析されます。decodeReplyFromBusboy関数は、このストリームにイベントエミッターをバインドし、生データが受信されるたびに ReactServerのデシリアライズハンドラ関数をトリガーします。decodeReplyFromBusboy の戻り値はchunk_0 です。await 演算子はこれを解決し、組み立てられた値をServer 。

getChunk関数は、指定されたIDに対応するチャンクを返します。そのチャンクがまだ存在しない場合、response._formDataにデータがすでに存在している場合はResolvedModelChunkを、そのIDに対するデータがまだ到着していない場合はPendingChunkを作成します。

decodeReplyFromBusboy が chunk_0 を返した時点では、そのチャンクはまだ PENDING 状態のままです。await 演算子は chunk_0.then() を呼び出し、resolve および reject コールバックを一時的に chunk_0.value および chunk_0.reason に格納します。これらのコールバックは、参照の解決が完了すると、wakeChunk 関数によって再起動されます。

シリアライズ解除プロセス - ReactServer
busboyStream が完全な生データフィールドを受信すると、「field」イベントエミッターをトリガーし、resolveField 関数を呼び出し、デシリアライゼーションを開始します。これにより、生のフォームデータが完全に構築された JavaScript オブジェクトに変換されます。このプロセスは、以下の関数によって制御されます。
resolveField(response, key, value)

キーと値は response._formData に追加されます。その後、この関数はキーと一致する ID に対応するチャンクを取得します。そのチャンクがすでに存在する場合、resolveModelChunk が呼び出され、チャンクが再構築されます。この遅延解決が必要な理由は、値に、生データがまだ到着していないフィールドへの参照が含まれている可能性があるためです。その場合、ReactServer は、後でそれらの参照を処理するために、カスタム resolve および reject コールバックを持つ PendingChunkServer 。
resolveModelChunk(chunk, value, id)

resolveModelChunk は、RESOLVED_MODEL 状態の ResolvedModelChunk を作成し、生データを注入します。その後、initializeModelChunk を通じてチャンクを再構築し、wakeChunk を呼び出してキューに登録された resolve および reject コールバックをトリガーすることで、オブジェクトまたは参照の解決を完了させます。
initializeModelChunk(chunk)

initializeModelChunk は、チャンクの状態を CYCLIC に移行させ(参照解決が進行中であることを示す)、デシリアライズを開始します。この関数は、JSON.parse を使用して chunk.value から生の JavaScript オブジェクトを構築し、そのオブジェクトを reviveModel 関数に渡します。
reviveModel(response, parentObj, parentKey, value, reference)

reviveModel は、解析されたオブジェクト内の各コンポーネントを再帰的に処理します。文字列値に遭遇すると、それを処理するために parseModelString を呼び出します。
parseModelString(response, obj, key, value, reference)

parseModelString は、文字列の接頭辞に基づいて処理を分岐させ、さまざまなエンコード形式に対応します。$ で始まる参照については、getOutlinedModel 関数が呼び出され、チャンク間の参照が解決されます。
getOutlinedModel(response, reference, parentObject, key, map)

getOutlinedModel は、「:」区切り文字で参照を分割してプロパティのアクセスパスを形成し、そのパスをターゲットのチャンクオブジェクト上で辿って、参照先の値を返します。以下の「脆弱性分析」で詳述されているように、これらのプロパティ名に対する検証が行われていない点が、まさにこの脆弱性が存在する原因となっています。
脆弱性分析
根本原因
CVE-2025-55182 originates from insufficient input validation in the getOutlinedModel() function within React’s server-side Flight reply handler (ReactFlightReplyServer.js). When a chunk reference includes a property path - such as $<id>:<prop1>:<prop2> - the function resolves it by traversing the specified properties on the target chunk object, computing the result as chunk[prop1][prop2].

重大な欠陥は、これらのプロパティ名が一切検証されない点にある。 サーバーは、要求されたプロパティがオブジェクト自身のプロパティなのか、継承されたプロトタイププロパティなのかを検証しません。そのため、攻撃者はプロパティパスに`__proto__`を含めることでプロトタイプチェーンを辿り、ユーザーが制御する入力からは決してアクセスできないはずの内部メソッドに到達することが可能です。例えば、参照 `$1:__proto__:then` は `Chunk.prototype.then` に解決されます。これは、攻撃者が制御された引数で呼び出すことができる関数です。

脆弱なコード
この攻撃チェーンは、ReactのFlightデシリアライズロジックにおける2つの異なるコードパスを悪用しています。
1つ目は Chunk.prototype.then です。これは、チャンクが thenable としてどのように振る舞うかを規定するものです。INITIALIZED 状態のチャンクに対して await が適用されると、resolve(chunk.value) が呼び出されます。chunk.value がそれ自体 thenable(.then()メソッドを持つオブジェクト)である場合、await 演算子は chunk.value.then() を再帰的に呼び出します。この再帰的な解決処理こそが、攻撃者が実行を任意の関数へとリダイレクトする仕組みとなっています。
2つ目は、parseModelString() 内の $B (blob) プレフィックス・ハンドラです:

ケース$Bでは、関数は response._formData.get(response._prefix + id) を呼び出します。_formData.get および _prefix は、チャンク内に格納されている _response オブジェクトのプロパティです。プロトタイプチェーンのトラバーサルを通じてこれらのプロパティを操作することで、攻撃者はこの呼び出しをリダイレクトし、任意のコードを引数としてグローバルな Function コンストラクタを呼び出すことができます。
開発
Through prototype chain traversal, an attacker reaches the global Function constructor via the path <any_object>.constructor.constructor. Because Chunk.prototype.then is a function, the path $1:constructor:constructor resolves to the global Function constructor, which accepts a string and returns a callable function containing that code.

実世界での影響の可能性を実証するため、当プログラムのフェローたちは、複数のセキュリティ研究チームによる独立した分析結果と整合する概念実証(PoC)ペイロードを構築しました。このエクスプロイトは2つの段階で動作します:
ステージ1 - 偽のチャンクを作成する:
The object delivered in field 0 acts as a fake chunk. Its then property is set to Chunk.prototype.then via the reference path $1:__proto__:then, allowing the Flight deserialization engine to invoke prototype-level behavior on this attacker-constructed object. The _response._formData.get property is pointed at the global Function constructor via $1:constructor:constructor, and _response._prefix is set to the malicious JavaScript code. The value field contains the string {"then": "$B0"}, instructing the blob handler to invoke itself on the same chunk when resolved. The status field is set to resolved_model so that initializeModelChunk is triggered when .then() is called, causing value to be parsed and the blob handler to fire.
この時点ではフィールド 1 がまだ受信されていないため、ReactServer は保留中の参照を処理するために、Server resolve および reject コールバックを作成します。
ステージ2 - トリガーの解決:
フィールド 1(チャンク 0 への生の参照である "$@0" を含む)が配信されると、保留中のチャンクが解決され、フェイクチャンクを直接指すようになります。これにより wakeChunk がトリガーされ、キューに登録されたコールバックが処理されるとともに、参照解決中にプロトタイプチェーンのトラバーサルが開始されます。 偽のチャンクが完全に解決されると、wakeChunkが再度呼び出されます。偽のチャンクの解決コールバックはNode.jsの暗黙的な解決関数であるため、チャンクの.then()メソッドを呼び出し、その値を解決します。これにより、最終的にFunctionコンストラクタを介して注入された悪意のあるコードが構築され、実行されます。
このエクスプロイトを実行するには、たった1回のHTTPリクエストだけで十分です:

Replacing {{COMMAND}} with any JavaScript code executes it on the server. The reason: -1 field prevents a toString() error during processing. The Next-Action header may contain any arbitrary value - even x - because the vulnerable deserialization occurs before the server validates the requested Server Function. This is what makes the vulnerability pre-authentication: the payload is processed during the deserialization phase, before any application-level authentication or authorization logic is reached.
攻撃が成功すると、攻撃者はサーバー上でNode.jsの実行コンテキストを完全に掌握することになります。これには、シェルコマンドの実行のための`child_process`へのアクセス権、データベースの認証情報やAPI を含む環境変数、ローカルファイルシステム、および横方向の移動を可能にするクラウドメタデータエンドポイントへのアクセス権が含まれます。
概念実証
当社の研究員は、create-next-app で生成され、本番環境向けにビルドされた標準的な Next.js アプリケーションを使用し、デフォルトの設定を変更することなく、管理された実験環境下でこの脆弱性を再現しました。この再現実験により、前述のシングルリクエスト型エクスプロイトペイロードによって、確実にリモートコード実行が可能であることが確認されました。


制御された実証実験により、脆弱性のあるNext.jsサーバーへのネットワークアクセス権を持つ攻撃者は、認証情報を提供したり、アプリケーションレベルの認証チェックをトリガーしたりすることなく、child_process.exec()を介したリバースシェルの生成、環境変数の読み取り、ローカルファイルシステムへのアクセスなど、任意のNode.jsコードを実行できることが明らかになった。 Next-Actionヘッダーに対して任意の値が受け入れられることは、この脆弱性が認証前の段階で発生するものであることをさらに裏付けています。つまり、サーバーはアクションの検索や認証チェックを行う前に、ペイロードを処理およびデシリアライズしてしまうのです。
緩和
Reactチームは、この脆弱性が公開されたのと同じ日である2025年12月3日にパッチをリリースしました。修正版はReact 19.0.1、19.1.2、および19.2.1として利用可能です。 このパッチでは、getOutlinedModel() および reviveModel() に関数プロパティの厳格な検証が追加され、Flight ペイロード内のユーザーが制御する参照パスからの継承プロトタイププロパティ(__proto__、constructor、prototype を含む)の解決が明示的にブロックされるようになりました。
組織は、直ちに以下の措置を講じるべきである:
- React パッケージをパッチ適用済みのバージョン(19.0.1、19.1.2、または 19.2.1)にアップグレードするには、状況に応じて `npm install react-server-dom-webpack@latest`、`react-server-dom-parcel@latest`、または `react-server-dom-turbopack@latest` を実行してください。
- フレームワークの依存関係をアップグレードする- Next.js、React Router、Waku、およびその他の影響を受けるフレームワークでは、対応するパッチがリリースされています。バージョンごとのアップグレード手順については、Reactチームのアドバイザリをご確認ください。
- ホスティングプロバイダーによる対策だけに頼らないでください。Vercelなどのプロバイダーは、脆弱性の公表後に一時的なWAFルールを適用しましたが、これらは一時的な措置に過ぎず、基盤となるパッケージへのパッチ適用に代わるものではありません。
- Next-Action ヘッダーを持ち、$@ または __proto__ パターンを含む multipart/form-data ボディを持つ POST リクエストについて、サーバーログを監査し、アプリケーションログ内で予期しない child_process または execSync の呼び出しがないか監視してください。
OPSWATによるリスク軽減
MetaDefender™ プラットフォームの独自技術であるOPSWAT 、CVE-2025-55182 のような脆弱性から防御するために必要な可視性と制御機能を提供します。 この脆弱性はオープンソースの npm パッケージ(react-server-dom-webpack、react-server-dom-parcel、react-server-dom-turbopack)に存在するため、組織は効果的な修正を行う前に、まずインフラストラクチャ全体でこれらのコンポーネントがどこにデプロイされているかを完全に把握する必要があります。

OPSWAT 、使用中のすべてのソフトウェアコンポーネント、ライブラリ、コンテナ、および依存関係の包括的な一覧を生成します。脆弱性のあるReactパッケージを含むアプリケーションやコンテナイメージをスキャンする際、システムは自動的にCVE-2025-55182を「重大」としてフラグ付けし、利用可能な修正バージョンに関するガイダンスを提供します。これにより、セキュリティチームは悪用される前に優先順位を付け、対策を講じることが可能になります。
OPSWAT 、個々のアプリケーションやコンテナイメージをスキャンする「MetaDefender 」と、開発ライフサイクル全体にわたるパイプラインレベルの可視化を実現する「MetaDefender Software Chain™」の両方で利用可能です。これらを組み合わせることで、セキュリティチームは以下のことが可能になります:
- 脆弱性のあるコンポーネントを迅速に特定- 脆弱性のあるバージョンのreact-server-dom-*パッケージを含むアプリケーションやコンテナを即座に特定し、デプロイメントの見落としを防ぎます。
- 積極的なパッチ適用を徹底する- オープンソースの依存関係を継続的に監視し、新しいアドバイザリが公開されるたびに、古くなったパッケージやセキュリティ上の脆弱性があるパッケージを検出し、リスクにさらされる期間を短縮する。
- コンプライアンスの維持とサプライチェーンの透明性確保- すべてのソフトウェアコンポーネントおよび既知の脆弱性に関する監査可能な記録を維持することで、規制要件を満たします。
CVE-2025-55182の悪用が示したその速度と規模(初週だけで5億8200万回以上の攻撃試行)は、事後対応的なパッチ適用だけではもはや不十分であることを如実に物語っています。自社の資産が何であるか、どこで実行されているか、そしていつ脆弱性が生じるかを把握することこそが、予防的な防御の基盤となります。その可視化は、SBOMから始まります。
参考文献
- Reactチーム - ReactServer における重大なセキュリティ脆弱性
- Wiz Research - React2Shell (CVE-2025-55182): Reactの重大な脆弱性
- トレンドマイクロ - CVE-2025-55182:React2Shellの分析、PoCの混乱、および実環境での悪用
- Akamai - CVE-2025-55182: React および Next.jsServer デシリアライゼーションによるリモートコード実行
- GoogleCloud 複数の攻撃者がReact2Shell(CVE-2025-55182)を悪用
- AWS - 中国とネクスス系のサイバー脅威グループがReact2Shellを急速に悪用
- NVD - CVE-2025-55182
- ReactServer 理解 - トニー・アリシア
- MDN Web Docs - await 演算子
- React ソースコード - ReactFlightReplyServer.js (v19.0.0)
- Next.js ソースコード - action-handler.ts (v16.0.0)
