エグゼクティブサマリー
本技術分析では、React Server Components(RSC)アーキテクチャにおける重大なリモートコード実行(RCE)脆弱性シナリオを検証します。この攻撃連鎖は、React Flightプロトコルのデシリアライズ処理における弱点を悪用し、クライアントが制御可能なペイロードの検証が不十分な場合に、攻撃者がサーバー側の実行を操作できる可能性があります。ReactとNext.jsは現代のWebインフラの大きな割合を支えているため、このような脆弱性は世界中のエンタープライズアプリケーション、ECプラットフォーム、政府サービスに影響を及ぼします。理論上、悪用に成功するには細工された単一のHTTPリクエストだけで十分で、従来のセキュリティ防御を回避できる可能性があります。
React Server Componentsの通信フロー
この図は、React Server Components(RSC)がReact Flightプロトコルを用いてクライアントと通信する方法を示しています。クライアントとサーバーは、シリアライズされたコンポーネントデータを増分チャンクとして交換し、それがサーバー上でデシリアライズされ再構築されます。React2Shellは、このシリアライズ/デシリアライズ処理の弱点を悪用してリモートコード実行を実現します。

1-React Server Components(RSC)アーキテクチャ
React Server Componentsは、コンポーネントをブラウザではなくサーバー上でのみ実行することで、Reactアーキテクチャにおけるパラダイムシフトをもたらします。JavaScriptをクライアントにバンドルする従来のReactコンポーネントとは異なり、RSCはサーバーランタイム(Node.jsまたはEdge環境)内で動作し、特権リソースへ直接アクセスできます:
- データベース接続および内部API
- ファイルシステム操作
- 環境変数および設定シークレット
- 内部サービス間通信
クライアントへHTMLやJavaScriptバンドルを送信する代わりに、RSCはUI構造を記述するシリアライズされたデータモデルを生成します。このモデルには、コンポーネント参照、props、状態情報、モジュール識別子が含まれ、実行可能コードではなく、クライアント側のReactランタイムに対する指示(命令)に相当します。
2 React Flightプロトコル:通信レイヤー
React Flightは、RSCアーキテクチャにおけるクライアントとサーバー間の輸送機構として機能します。このバイナリ風プロトコルは次を扱います:
- 増分チャンクによるコンポーネントデータのストリーミング
- UI更新の同期
- シリアライズされたコンポーネントツリーの送信
このプロトコル設計は、Flightペイロードが信頼できるReactクライアントから送られてくることを前提としています。しかし、RSCエンドポイントはHTTPエンドポイントとして直接公開されていることが多く(Next.jsアプリケーションでは一般に/_rscや類似パス)、悪意ある攻撃者が任意のFlightペイロードを作成して送信できる攻撃面が生まれます。
セキュリティ上の含意:重要な信頼境界は、Flightペイロードがサーバー上でデシリアライズされる地点に存在します。React2Shellは、再構築時にサーバーが誤って信頼してしまう悪意あるシリアライズデータを注入することで、まさにこの境界を突きます。
3. シリアライズとデシリアライズ
シリアライズとは、メモリ上のサーバー側オブジェクト(コンポーネント、props、参照など)をネットワーク送信に適した形式へ変換するプロセスです。
デシリアライズはその逆のプロセスです:
- 受信したFlightペイロードを解析する
- オブジェクトをメモリ上に再構築する
- 再構築された構造に基づいて実行を継続する
React Server Componentsの文脈では、デシリアライズは複雑です。なぜなら次を含み得るからです:
- オブジェクトグラフの再構築
- モジュール参照の解決
- 内部実行ロジックのリンク
セキュリティ上の含意(React2Shell):
React2Shellは、攻撃者が制御するデータが厳格な検証なしに信頼され再構築される安全でないデシリアライズを悪用します。これにより悪意あるペイロードが実行フローに影響を与え、最終的にリモートコード実行を達成します。
4.
データチャンク
React Flight
は、すべてのデータを単一のレスポンスで送信しません。代わりに、シリアライズされた
データは小さな
チャンク
に分割され、クライアントとサーバー間で増分的にストリーミングされます。
各チャンクは:
- シリアライズされたコンポーネントツリーの一部を含む
- 独立して解析・処理される
- 最終的なUI状態の再構築に寄与する
この
チャンクベースのアプローチは、次を改善します:
- パフォーマンス
- 段階的レンダリング
- ユーザー体験
React2Shellとは?
React2Shellは、React Server
Componentsにおける重大なリモートコード実行(RCE)脆弱性であるCVE-2025-55182の通称です。これはゼロデイ攻撃であり、未認証の攻撃者が、細工された単一のHTTPリクエストを通じて脆弱なサーバー上で任意のコードを実行し、サーバー環境を完全に制御できるようになります。
本質的には、
React2Shellは、React Flight Protocolがデータをデシリアライズする方法の欠陥を悪用し、攻撃者がプロトタイプチェーンを操作して悪意あるコードを注入し、それが通常のサーバー側レンダリング処理中に実行されるようにします。
影響を受けるソフトウェア
上の表は、CVE-2025-55182
(React2Shell)の影響を受けるコンポーネントとソフトウェアバージョンを示しています。
この脆弱性は主に、React
Server Components(RSC)を使用し、サーバーからクライアントへの通信にReact
Flightプロトコルを依存している環境に影響します。
問題の中核は、React
19.0.0から19.2.0にあり、Flightプロトコル内の安全でないデシリアライズロジックにより、攻撃者が制御するペイロードがサーバー側の実行に影響を与えられます。これにより、これらのバージョンでRSCを実行しているあらゆるアプリケーションが、未認証のリモートコード実行に直接さらされます。
App Routerを使用するNext.jsアプリケーションは特に露出が大きいです。16.0.0
から16.0.6、ならびに15.xおよび初期の16.xリリースには、デフォルトでReact Flight
エンドポイントを公開する脆弱なRSC統合が含まれています。Canary
ビルド(14.x)も、実験的なRSC機能のため影響を受けますが、後続のcanaryリリースではパッチが適用されています。
フレームワークレベルの露出に加え、この脆弱性はReact
Server DOMシリアライズライブラリにも及びます。具体的には:
- react-server-dom-webpack
- react-server-dom-parcel
- react-server-dom-turbopack
これらのライブラリは、React Flightで使用される基盤となるシリアライズ/デシリアライズロジックを実装しています。フレームワークのバージョンに関係なく、明示的に更新されるまで、ベンダーパッチ以前にリリースされたすべてのバージョンは脆弱と見なすべきです。
| コンポーネント | 製品/ライブラリ | 脆弱なバージョン | 修正済みバージョン |
|---|---|---|---|
| React Server Components | React | 19.0.0 – 19.2.0 | 19.2.1+ |
| App Router | Next.js | 16.0.0 – 16.0.6 | 16.0.7+ |
| Canary Builds | Next.js | 14.x Canary | 後続のcanaryリリースでパッチ適用 |
| Stable Releases | Next.js | 15.x, 初期16.x | 16.0.7+ |
| Serialization Layer | react-server-dom-webpack | パッチ適用リリース以前の全バージョン | ベンダーパッチが必要 |
| Serialization Layer | react-server-dom-parcel | パッチ適用リリース以前の全バージョン | ベンダーパッチが必要 |
| Serialization Layer | react-server-dom-turbopack | パッチ適用リリース以前の全バージョン | ベンダーパッチが必要 |
React2Shellのエクスプロイトに入る前に、まずJavaScriptの基本概念である「関数がどのように定義され、実行されるか」を理解する必要があります。この知識は重要です。なぜなら、React2Shellの攻撃連鎖は、特に危険なFunction()コンストラクタを含む、異なる関数定義方法の特定の挙動を悪用するからです。
JavaScriptの関数定義
JavaScriptには複数の関数定義方法があり、それぞれ特性が異なります。中核となる違いは、宣言(コンパイル時に解析される)と、式/コンストラクタ(実行時に評価される)です。Functionコンストラクタは、文字列を実行可能コードとして評価するため特に危険です。
1. 関数宣言
名前とコードブロックを持つ標準的な関数宣言。最も安全な方法で、解析フェーズ中にコンパイルされます。
function func1() { console.log("Resecurity");}

2. 関数コンストラクタ
文字列引数を伴うグローバルFunctionコンストラクタを使用。実行時に動的にコードを評価します
var func2=Function("console.log(`Resecurity`)")

3. プロトタイプ経由のコンストラクタアクセス
alert関数のプロトタイプチェーンを通じてFunctionコンストラクタへアクセスします。任意のオブジェクトがコード実行へ到達できることを示します。
var func3=alert.__proto__.constructor("console.log('Resecurity')")

4. 二重コンストラクタチェーン
二重の.constructorアクセスでも同じFunctionコンストラクタに到達します。プロトタイプチェーンの一貫性を示します。
var func4=alert.__proto__.constructor.constructor("console.log('Resecurity')")

5. メソッドのコンストラクタ
メソッドのプロトタイプを通じてFunctionコンストラクタへ到達します。任意の関数プロパティが同じ実行エンドポイントへつながることを示します。
5var func5=alert.__proto__.toString.constructor("console.log('Resecurity')")

React2Shell PoC – 技術的ウォークスルー
1: 通常のFlightチャンク解決
React Flightが通常どのようにチャンク間の参照を解決するかを理解することは、攻撃者がこのプロセスをどのように覆すかを見る上で不可欠です。
ペイロード例
files = {
"0": (None, '["$1"]'),
"1": (None, '{"object":" company ","name":"$2: companyName "}'),
"2": (None, '{"companyName":"Resecurity"}'),
}
$<id>は、Reactに対してIDで別のチャンクを解決するよう指示します。$<id>:keyは、参照先チャンクから特定のプロパティを取得します。- 解決はモデル復元(model revival)中に行われ、Reactがシリアライズデータからコンポーネントツリーを再構築します
手順
1. サーバーが3つのチャンク(0, 1, 2)を受信
2. チャンク0がチャンク1を参照:["$1"]
3. チャンク1の内容:{object:"company", name:"$2:companyName"}
4. $2:companyNameの意味:チャンク2を取得し、その後「companyName」プロパティへアクセス
5. チャンク2の内容:{"companyName":"Resecurity"}
6. 最終的なデシリアライズ結果:{object:"company", name:"Resecurity"}
これは意図された利用方法を示しています。チャンクは$id:propertyPath構文により、他チャンクのプロパティを参照できます。システムは、すべての参照が正当なデータプロパティを指し、JavaScript内部ではないことを信頼しています。
{ object: " company ", name: "Resecurity" }
2: デシリアライズ中のプロトタイプ走査
中核となる脆弱性:Reactは、参照されたプロパティがアクセス前に対象オブジェクト上に実在するかを検証していませんでした。
ペイロード
files = {
"0": (None, '["$1:__proto__:constructor:constructor"]'),
"1": (None, '{"x":1}'),
}
手順
- 1:
$1がチャンク1に解決 →{x: 1} - 2:
:__proto__がJavaScript組み込みのプロトタイププロパティへアクセス
→ Object.prototype(すべてのオブジェクトの親)を返す
- ステップ3:
:constructorがObject.prototypeのconstructorプロパティへアクセス
→ Object()コンストラクタ関数を返す
- ステップ4:
:constructorを再度適用し、Object()関数のconstructorへアクセス
→ グローバルFunction()コンストラクタを返す
- 結果:攻撃者がFunctionコンストラクタ [Function: Function] を取得
なぜ成立するのか: JavaScriptのプロトタイプチェーンは常にアクセス可能です。obj.propertyにアクセスすると、JavaScriptは次を行います:
obj自身がpropertyを持つか確認- なければ
obj.__proto__(プロトタイプ)を確認 - 見つかるまでチェーンを上へ辿るか、
undefinedを返す
3: Thenable(Thenable)— データを実行へ変換する
「thenable」とは、.thenメソッドを持つ任意のJavaScriptオブジェクトのことです。実際のPromiseである必要はありません。JavaScriptがawait objに遭遇した際、obj.thenが存在すれば、言語仕様により自動的に.then()が2つの内部コールバック関数(resolveとreject)とともに呼び出されます。この組み込み挙動により、脆弱なコード内に明示的な関数呼び出しが見当たらなくても、攻撃者は関数実行をトリガーできます。
プリミティブを示すペイロード例
files = {
"0": (None, '{"then":"$1:__proto__:constructor:constructor"}'),
"1": (None, '{"x":1}'),
}
これにより次のエラーが発生:

手順
1: デシリアライズ結果:{then: Function}
2: アプリケーションコードが実行:await decodedReply
3: JavaScriptランタイムが.thenプロパティを検出
4: 自動的に呼び出し:Function(resolve, reject)
5: Function()が引数をコードとして実行しようとする
結果:SyntaxError(resolve/reject関数は有効なコード文字列ではない)
4: $@chunkId — オブジェクト境界の突破
React Flightの特殊構文を使い、解決済みの値ではなく生のチャンクオブジェクトを参照します。$@<id> → 解決済み値ではなく、生のチャンクオブジェクトを返す
ペイロード
files = {
"0": (None, '{"then": "$1:__proto__:then"}'),
"1": (None, '"$@0"'),
}
$@構文の説明
React Flightは、チャンクを解決せずにアクセスできる特別な参照構文を提供します。
通常の参照解決
$1
- チャンク1を解決する
- 最終的なデシリアライズ値を返す
- チャンク内の参照はすべて既に処理済み
例:$1 → { x: 1 }
これはコンポーネント再構築のための安全で意図された挙動です。
特殊な生参照構文
$@1
- 生のチャンクオブジェクトそのものを返す
- 通常の解決をバイパスする
- 内部メタデータやメソッドがそのまま残る
$@が存在する理由
Reactは内部的に$@を次の目的で使用します:
- ストリーミングと増分レンダリングのサポート
- 未解決チャンクを受け渡しできるようにする
- 非同期協調のためにチャンクをthenableとして扱う
正当な利用では、これは決して信頼できない入力に公開されません。
React2Shellにおいて$@が危険な理由
攻撃者がFlightペイロードを制御できる場合:
- $@はデータとランタイム内部の抽象化境界を破壊する
- 攻撃者は次へアクセスできる:
- チャンクのプロトタイプ
.then()実装statusや_responseなどの内部状態フィールド
5: initializeModelChunk()の強制実行
Reactには、initializeModelChunk()関数を通じて「解決済みモデル」チャンクを処理する特別な内部ロジックがあります。攻撃者は、細工したチャンクに特定のstatusフィールドを設定することで、Reactにこの重要関数を実行させられることを発見しました。これにより、異なる実行コンテキストで2回目のデシリアライズが行われるようになります。
files = {
"0": (None, '{"then":"$1:__proto__:then","status":"resolved_model"}'),
"1": (None, '"$@0"'),
}
内部ロジック
Reactは次を確認:
chunk.status === "resolved_model"
if (chunk.status === "resolved_model") {
initializeModelChunk(chunk); // <-- トリガーされる!
// initializeModelChunkの内部:
var parsed = JSON.parse(chunk.value); // chunk.valueをパース
reviveModel(chunk._response, parsed); // 2回目のデシリアライズ!
}
手順
1: チャンクのstatusが"resolved_model"
2: ReactがinitializeModelChunk(chunk)を呼び出す
3: initializeModelChunkがchunk.valueをJSONとしてパース
4: chunk._responseをコンテキストとしてreviveModel()を呼び出す
結果: 異なるコンテキストで2回目のデシリアライズが発生する
statusフィールドはクライアント制御であり、検証がありません。Reactは、自身のコードだけがstatus: "resolved_model"を設定すると想定しています。
6: _responseによるコンテキスト混乱
_responseオブジェクトは、React Flightのデシリアライズ処理における実行コンテキストとして機能します。悪意ある_responseオブジェクトを提供することで、攻撃者はチャンク参照が解決される環境を完全に制御でき、通常のデータ操作をコード実行へと誘導できます。

主要コンポーネントの説明:
- reason: -1:
initializeModelChunk内のエラーチェックを回避:
var rootReference = chunk.reason.toString(16); // -1.toString() はエラー
- value:
'{"then": "$B0"}':2回目のパスでパースされるJSON。$B0は「blob」(Flightの特殊型)を参照します。 _response:2回目のパスの実行コンテキスト。完全に攻撃者が制御可能。_response._formData.get:プロトタイプチェーン経由でFunction()コンストラクタを指す。_response._prefix:Function()に渡される引数になる。
これは「Confused Deputy(混乱した代理人)」攻撃です: reviveModel()関数(代理人)は攻撃者制御の_responseオブジェクトを使用しますが、サーバー権限で実行されます。
7: Blob解決 → Function実行
React Flightは、$Bで始まる「blob参照」を通じてバイナリデータを特別扱いします。攻撃者は、blob解決メカニズムを乗っ取り、_responseコンテキストを制御することで、無害なデータ取得操作を、攻撃者が制御するコードを引数とするFunction()コンストラクタ呼び出しへ直接変換できることを発見しました。
// 簡略化したblobハンドラのロジック(脆弱バージョン)
case "B": // "$B0" のようなblob参照を処理
// BUG: response._formData.get は攻撃者が制御可能
return response._formData.get(response._prefix + blobId);
手順
1: 2回目のパスが値{"then": "$B0"}を処理
2: $B0がblobハンドラをトリガー
3: response._formData.get → Function()コンストラクタに解決
4: response._prefix + "0" → "RCE_CODE_HERE" + "0"
5: Function("RCE_CODE_HERE0")が呼ばれる
6: 攻撃者コードを含む関数が生成される
7: 関数が.thenプロパティとして返る
8: awaitが自動的に.then()を呼び出す
9: 攻撃者コードが実行される
8: 実際のRCEペイロード例
この最終ステップは、プロトタイプ汚染、thenableの挙動、チャンク操作、blob解決を単一の武器化ペイロードに統合し、サーバー上で任意のNode.jsコードを実行します。細工されたペイロードは、React Flightのデシリアライズ処理の各段階を悪用して、完全なリモートコード実行(RCE)を達成します。

各コンポーネントの役割
1. "then": "$1:__proto__:then"
.thenプロパティを乗っ取り、危険なグローバルFunction()コンストラクタではなく、React内部のChunk.prototype.thenメソッドを指すようにします。
2. "status": "resolved_model"
悪意あるチャンクを完全に解決済みのモデルチャンクとしてReactに扱わせ、特別なinitializeModelChunk()関数をトリガーします。
単純なデータオブジェクトとしてではなく。このstatusを設定することで、実質的にReactへ「このチャンクは高度な処理の準備ができている」と伝え、chunk.valueをJSONとしてパースし、ネストした参照を2回目に処理する能力を解放します。
3. "reason": -1
initializeModelChunk()内での早期エラーを防ぐ、微妙ですが不可欠なバイパスです。
4. "value": '{"then": "$B0"}'
2回目のデシリアライズパス中に処理されるblob参照($B0)を含むネストしたJSON構造を含みます。
5. "_response"
目的: 2回目のデシリアライズパスのために、完全に攻撃者が制御する実行コンテキストを提供します。
6. "_formData.get"
グローバルFunction()コンストラクタを指し、データ取得操作をコード実行ガジェットへ変換します。
7. "_prefix"
Function()コンストラクタへ渡される実際のリモートコード実行ペイロードを含みます。
8. "$@0"
自己参照ループを作成し、解決プロセス中にチャンク0が自分自身を参照できるようにします。
各コンポーネントは、Reactアーキテクチャにおける異なる信頼の前提を悪用し、組み合わさることで、無害に見えるデータをリモートコード実行へ変える武器化されたデシリアライズ連鎖を形成します。このエクスプロイトの巧妙さは、バッファオーバーフローやメモリ破壊、従来のエクスプロイト手法を一切用いずに、チャンク処理、thenable処理、blob解決といったReactの正当な機能そのものを逆用している点にあります。
React2Shell Lab POC:コマンド実行デモ

React2Shellライブ攻撃:本番サーバー侵害

結果: サーバー上でidコマンドを実行
CVE-2025-55182がビジネス上クリティカルである理由
1. 世界規模の大規模露出
ReactとNext.jsは、エンタープライズアプリケーション、ECプラットフォーム、SaaS製品、政府サービスを含む現代のWebインフラの相当部分を支えています。
React2ShellはReact Server Components(RSC)を標的とするため、悪用に成功すると、単一の機能やエンドポイントに留まらず、アプリケーションのバックエンドランタイムが完全に侵害されることが多いです。
多くの組織にとって、RSCの侵害はアプリケーション全体の完全掌握に等しいものです。
2. 低摩擦・高インパクトの悪用
React2Shellに必要なのは:
- メモリ破壊なし
- 認証なし
- 昇格権限なし
- React Flightのデシリアライズにおけるロジック欠陥を突く、細工された単一のHTTPリクエストだけでリモートコード実行が可能
公開PoCはすでに利用可能であり、悪用の障壁を大きく下げ、便乗的・自動化された攻撃の可能性を高めています。
3. 従来の防御は無効
このエクスプロイトは、アプリケーションレベルの検証、ルーティングロジック、認可チェックが行われる前のデシリアライズ中に実行されます。
その結果:
- 多くのWAFはデフォルトでは攻撃を検知しない
- シグネチャベースのルールはペイロード難読化で容易に回避される
- アプリケーションログは初期実行イベントを完全に見逃す可能性がある
React Flightの内部に対する明確な理解がなければ、多くの防御レイヤーはほとんど、あるいは全く保護を提供できません。
4. スタックのあらゆる層に及ぶ影響
React2Shellの悪用に成功すると、攻撃者は次が可能になります:
- サーバー上で任意のコードを実行
- 環境変数やクラウド認証情報へアクセス
- データベースの読み取り・改ざん
- ランサムウェアや永続的なWebシェルの展開
- 環境内での水平移動の足掛かりを確立
今すぐ環境を保護する方法
1. 公式パッチを直ちに適用
ReactおよびNext.jsチームは修正をリリースしています。すべての環境が次へアップグレードされていることを確認してください:
- React:
19.2.1以降 - Next.js:
16.0.7以降
環境が未パッチのままであれば、反証されるまで侵害の可能性があると見なしてください。
2. シークレットと認証情報を再生成
悪用によりサーバーメモリと環境変数へアクセスできるため、認証情報の漏えいは前提とすべきです。
直ちに次をローテーションしてください:
- APIキー
- 環境変数
- データベース認証情報
- クラウドアクセストークン(AWS、GCP、Azureなど)
可能な限り古い認証情報を無効化してください。
3. WAFおよびAPIゲートウェイの保護を実装
次に焦点を当てた一時的な検知・遮断ルールを展開してください:
- 疑わしい、または不正なReact Flightチャンク構造
__proto__やプロトタイプ関連参照を含むリクエストnext-action、rsc-action-id、または関連ヘッダーの異常な使用
これらの制御はすべての亜種を止められるわけではありませんが、大規模悪用やスキャン活動を妨害できます。
4. RSCおよびNext.jsデプロイを強化
ランタイムのハードニングを徹底して被害範囲を縮小します:
- RSCサーバーを最小限のOS/クラウド権限で実行する
- アプリケーション、データベース、クラウド制御プレーン間の強固な分離を強制する
- 可能であれば読み取り専用ファイルシステムを使用する
5. 侵害指標(IoC)を能動的に探索
ログとテレメトリを監視し、次を確認してください:
- 予期しない、または異常な
.then()の挙動 - RSCエンドポイントを狙う不審なPOSTリクエスト
- Node.jsプロセスからのシェルコマンド実行
- アプリケーションサーバーからの異常な外向きネットワークトラフィック
結論
React2Shellは、高度なアーキテクチャにもかかわらず、現代のフレームワークが古典的な脆弱性を引き継ぎ得ることを示しています。この仮想的なRCE欠陥は、クライアントデータへの暗黙の信頼とプロトタイプ検証の欠如に起因し、攻撃者がデシリアライズを通じて実行を操作できるようにします。この攻撃連鎖は、洗練された機能が攻撃面を拡大し、従来の防御がプロトコル固有の攻撃に対して機能しないことを明らかにします。組織は、ゼロトラストのデシリアライズを採用し、依存関係の監視を優先し、プロトコルを理解したセキュリティ制御を実装する必要があります。最終的に、複雑なWebエコシステムにおいてセキュリティは付随的なものではなく基盤であるべきであり、デシリアライズは継続的な精査と多層防御を要する重要な脅威ベクトルとして扱われなければなりません。