React Router のプロトタイプ汚染から RCE へ:CVE-2026-42211

turbo-stream v2 は TYPE_ERROR の復元でコンストラクタを動的に呼び出しながら、その出所を検証していません。アプリにプロトタイプ汚染が一つあればこの経路が Function に届き、中度の欠陥二つが RCE になります。

概要

項目 内容
CVE CVE-2026-42211
CVSS 8.1(High)
CWE CWE-502 信頼できないデータのデシリアライズ
影響版 React Router 7.0.0 〜 7.14.1(Framework Mode のみ)
修正版 7.14.2
公開日 2026-06-03
実悪用 公開報告なし

React Router は React エコシステムで最も広く使われるルーティングライブラリで、週あたり 1 億 2 千万回以上ダウンロードされます。2026 年 6 月 3 日に公開されたこの脆弱性には決定的な特徴があります。単独では発火しません。プロトタイプ汚染と安全でないデシリアライズが、この順で揃う必要があります。

深刻さを決めるのは露出面です。Framework Mode は公式に推奨される既定のモードであり、2024 年 10 月以降に作られた多くの React フルスタックプロジェクトがこの攻撃面を既定で抱えています。公開後、CVE Intruder の注目一覧で一時的に首位に立ちました。

発見の経緯

GitHub Security Lab の研究者 @securityMB が、Framework Mode に組み込まれたシリアライズライブラリ turbo-stream v2 を監査しているときに見つけました。

通常の動作では TYPE_ERROR の分岐は TypeError オブジェクトを再構築するだけです。しかしこの分岐は、データを復元するためにコンストラクタを動的に呼び出し、その出所を検証していません。したがってアプリ内の任意の独立したプロトタイプ汚染によって Error.prototype 連鎖へプロパティを注入できれば、想定されたコンストラクタを環境内の任意のコンストラクタ、たとえば Function に置き換えられます。

そして Function は文字列引数を受け取ると JavaScript をそのまま実行します。連鎖の最後の環はそこで閉じます。

再現

以下はすべて、許可されたセキュリティテストに限ります。

公式のスキャフォールドで影響版を用意します。

npx create-react-router@7.14.1 vulnerable-app
cd vulnerable-app && npm install && npm run dev

第一段階:前提となる汚染を成立させる。 実験室では、古い jQuery や旧版 lodash の残骸を安全でない深いマージで再現します。

function deepMerge(t, s) {
  for (const key in s) {
    if (typeof s[key] === 'object' && s[key] !== null) {
      t[key] = t[key] || {};
      deepMerge(t[key], s[key]);
    } else {
      t[key] = s[key];
    }
  }
}

__proto__ や constructor.prototype を通常のキーとして扱い、そのまま書き込みます。まさに侵入経路です。

第二段階:デシリアライザを発火させる。 汚染が入った状態で、細工した turbo-stream データを Framework Mode のデータ端点へ送り、処理を TYPE_ERROR の分岐へ導きます。汚染されたコンストラクタが想定の型に取って代わり、Function が呼ばれ、渡された文字列がコードになります。公開された PoC はこの構造を Python で組み立て、一行で検証します。

python3 poc_cve_2026_42211.py "id"

第三段階:確認。 サーバープロセスの識別情報、たとえば uid=1000(node) のような出力が得られれば、偽造した応答ではなく実際にコードが実行されたことになります。

修正

修正版は 7.14.2 です。パッチは文書化されていなかった独自のエラーシリアライズ処理を取り除き、連鎖を根元で断ちます。

npm install react-router@7.14.2
npm list react-router

すぐに更新できない場合は、宣言型モード(<BrowserRouter>)やデータモード(createBrowserRouter / <RouterProvider>)へ退避します。どちらも影響を受けません。あわせて Framework Mode 端点の外部からの到達性を絞ります。

最後に汚染そのものを探します。依存スキャンをすべての深いマージ経路($.extend(true, ...)、_.merge など)に向け、マージ前に __proto__ constructor prototype を明示的に拒否します。前提条件を残せば、同種の問題は別の形で戻ってきます。

判断

CVE-2026-42211 は足し算です。プロトタイプ汚染は単独では低、安全でないデシリアライズは単独では中、そして連鎖すれば遠隔コード実行です。所見を一つずつ採点するだけでは足りません。二つを重ねられるかどうかもリスクの一部であり、その組み合わせは個々のスコアには決して現れません。

← 記事一覧に戻る

コメント

…