Fragments of verbose memory

冗長な記憶の断片 - Web技術のメモをほぼ毎日更新

Dec 10, 2025 - 日記

「React2Shell」は偶然ではない:サーバーコンポーネント時代のセキュリティ設計を問い直す

react2shell-security-vulnerability cover image

React のサーバーコンポーネント(React Server Components、RSC)で、リモートコード実行(RCE)を可能にする重大な脆弱性「React2Shell」が発見されました。この脆弱性は単なるバグではなく、クライアントサイドとサーバーサイドの境界を曖昧にする現代のフレームワーク設計が抱える根本的な問題を浮き彫りにしています。本記事では、React2Shell(CVE-2025-55182 )の技術的詳細と、サーバーコンポーネント時代のセキュリティ設計について考察します。

React2Shell脆弱性の概要

基本情報

  • CVE番号:
    • CVE-2025-55182 (React Server Components)
    • CVE-2025-66478(Next.js)
  • 発見者: Lachlan DavidsonMeta Bug Bounty 経由で報告)
  • 技術分析: Wiz ResearchPalo Alto Networks Unit 42
  • 影響範囲: React Server Components を使用するアプリケーション
  • 深刻度: Critical(CVSS基本値 10.0
  • 攻撃可能性: ネットワーク経由、認証不要
  • 影響: サーバー上での任意のコード実行

脆弱性の名称「React2Shell」は、攻撃者がReactアプリケーションを介してサーバーシェルを取得できることに由来します。

影響を受けるバージョン

以下のReactパッケージのバージョンで脆弱性が確認されています:

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

Next.js: 15.0.0以降の全バージョン(16.0.xまで)が影響を受けます。

注意: React 18系とNext.js 14.3.0-canary.77より前のバージョンは影響を受けません。

技術的な仕組み

React Server Componentsとは

React Server Components(RSC)は、サーバー側でのみレンダリングされるReactコンポーネントです。従来のクライアント側レンダリング(CSR)やサーバーサイドレンダリング(SSR)とは異なり、コンポーネントの実行がサーバーに閉じられるため、以下の利点があります:

  • データベースへの直接アクセス
  • バックエンドリソースの直接利用
  • 機密情報(APIキー等)をクライアントに露出しない
  • バンドルサイズの削減

しかし、この「サーバー専用」という制約が、今回の脆弱性の発生源となりました。

脆弱性の根本原因

React2Shellの本質は、クライアントから送信されたJSONペイロードをサーバー側で実行可能なコードとして解釈してしまうという設計上の欠陥にあります。

通常、RSCでは以下のようなフローでサーバーコンポーネントを処理します:

  1. クライアントがサーバーコンポーネントをリクエスト
  2. サーバーがコンポーネントをレンダリング
  3. レンダリング結果をシリアライズしてクライアントに送信
  4. クライアント側でデシリアライズして表示

問題は、ステップ4のデシリアライズ処理にあります。React 19では、デシリアライズ時に特定の条件下でクライアント提供のデータを実行可能コードとして評価してしまう欠陥がありました。

攻撃の実例

Palo Alto Networks Unit 42の技術レポート では、以下のような攻撃シナリオが示されています:

脆弱なサーバーコンポーネントの例:

// app/page.tsx (サーバーコンポーネント)
export default async function Page({ searchParams }) {
  const userId = searchParams.userId;
  
  // データベースからユーザー情報を取得
  const user = await db.getUser(userId);
  
  return <UserProfile user={user} />;
}

一見安全に見えるコードですが、攻撃者が細工したリクエストを送信すると:

GET /page?userId=__proto__.constructor.constructor('require("child_process").execSync("whoami")')()

このリクエストにより、サーバー側でchild_process.execSyncが実行され、任意のシェルコマンドが実行される可能性がありました。

攻撃フローの視覚化

以下の図は、React2Shell攻撃の流れを示しています:

React2Shell攻撃フロー

攻撃は4つのステップで構成されます:

  1. 攻撃者: 細工したHTTPリクエストを送信(悪意のあるペイロードを含む)
  2. Next.jsサーバー: React Server Componentでペイロードをデシリアライズ
  3. RCE発生: デシリアライズ処理で悪意のあるコードが実行される
  4. 侵害完了: 攻撃者がサーバーシェルアクセスを取得

このように、単純なHTTPリクエストからサーバーの完全な侵害まで、わずか数ステップで到達できてしまうのが、この脆弱性の恐ろしさです。

なぜこの脆弱性は重大なのか

1. 攻撃の容易さ

  • 認証不要: 攻撃者は認証なしでエクスプロイトを実行可能
  • 単純なHTTPリクエスト: 特殊なツールや高度な技術は不要
  • 広範な影響: Next.js 15を使用する全てのアプリケーションが潜在的に影響

2. サーバーへの完全な侵害

RCE(Remote Code Execution)脆弱性のため、以下が可能になります:

  • 環境変数の読み取り(APIキー、データベース認証情報等)
  • ファイルシステムへのアクセス
  • ネットワークスキャンやラテラルムーブメント
  • バックドアの設置

3. 検出の困難さ

通常のWebアプリケーションファイアウォール(WAF)では検出が困難です。攻撃ペイロードはHTTPリクエストパラメータとして正当に見える形で送信されるためです。

影響を受けるアプリケーションの特定

以下の条件を満たすアプリケーションは、脆弱性の影響を受ける可能性があります:

# Next.jsバージョンの確認
npm list next

# Reactバージョンの確認
npm list react

以下のバージョンは直ちにアップデートが必要です:

  • Next.js 15.0.0 ~ 15.1.0
  • React 19.0.0(Next.js 15と併用時)

対策と修正方法

即時対応

  1. パッケージのアップデート
# Next.jsを最新版にアップデート
npm install next@latest

# Reactを最新版にアップデート(Next.js 15使用時)
npm install react@latest react-dom@latest

修正版:

  • Next.js 15.1.1以降
  • React 19.0.1以降
  1. デプロイの実施

アップデート後は、速やかに本番環境へデプロイしてください。

長期的な対策

入力検証の強化

サーバーコンポーネントで外部入力を扱う際は、厳密なバリデーションを実施します:

import { z } from 'zod';

// スキーマ定義
const userIdSchema = z.string().uuid();

export default async function Page({ searchParams }) {
  // バリデーション
  const result = userIdSchema.safeParse(searchParams.userId);
  
  if (!result.success) {
    return <ErrorPage message="Invalid user ID" />;
  }
  
  const user = await db.getUser(result.data);
  return <UserProfile user={user} />;
}

最小権限の原則

サーバーコンポーネントが実行される環境では、必要最小限の権限のみを付与します:

  • データベースアクセスは読み取り専用アカウントを使用
  • ファイルシステムアクセスは特定ディレクトリに制限
  • 環境変数は必要なもののみ設定

セキュリティモニタリング

不審なリクエストパターンを検出するため、以下を実装します:

// middleware.ts(Next.js)
import { NextResponse } from 'next/server';

export function middleware(request) {
  const url = new URL(request.url);
  
  // 疑わしいパターンを検出
  const suspiciousPatterns = [
    /__proto__/,
    /constructor/,
    /execSync/,
    /require\(/,
  ];
  
  for (const param of url.searchParams.values()) {
    for (const pattern of suspiciousPatterns) {
      if (pattern.test(param)) {
        // ログ記録とブロック
        console.warn('Suspicious request detected:', param);
        return new NextResponse('Forbidden', { status: 403 });
      }
    }
  }
  
  return NextResponse.next();
}

サーバーコンポーネント時代のセキュリティ設計

React2Shellは、単なる実装バグではなく、フレームワーク設計における構造的な問題を示唆しています。

クライアント・サーバー境界の曖昧化

従来のWebアプリケーションでは、クライアントとサーバーの境界が明確でした。しかし、RSCのような技術では:

  • 同一コンポーネント内でサーバー処理とクライアント処理が混在
  • 開発者がどのコードがどこで実行されるか意識しづらい
  • セキュリティリスクの見落としが発生しやすい

新しいセキュリティモデルの必要性

サーバーコンポーネントを安全に使用するためには、以下の原則が重要です:

  1. ゼロトラスト原則: クライアントからの全ての入力を信頼しない
  2. 型安全性の徹底: TypeScriptとランタイムバリデーションの併用
  3. レイヤー分離: ビジネスロジックとプレゼンテーションの明確な分離
  4. セキュリティバイデザイン: フレームワークレベルでの安全な実装

まとめ

React2Shell脆弱性は、「偶然」発見されたバグではなく、サーバーコンポーネントという新しいパラダイムが内包する構造的リスクが顕在化したものです。

今後、同様のフレームワーク(RemixSvelteKit 等)でも類似の問題が発見される可能性があります。開発者は、フレームワークの利便性に依存するだけでなく、その背後にあるセキュリティメカニズムを理解し、適切な対策を講じる必要があります。

まずは、Next.js 15およびReact 19を使用している環境では、速やかにアップデートを実施してください。そして、サーバーコンポーネントを使用する際は、常に「このデータは信頼できるか?」という問いを意識することをお勧めします。

参考リンク