本ドキュメントでは、React Server Components (RSC) における深刻な脆弱性(CVE-2025-55182)に対し、React 19.0.1 で行われた具体的な修正内容をソースコードレベルで解説します。
主な修正方針は、Flightプロトコル(RSC通信)のデシリアライズ処理における hasOwnProperty チェックの徹底と、参照解決フローの厳格化によるプロトタイプ汚染およびRCE(リモートコード実行)の防止です。
概要: createModelResolver を廃止し、より堅牢な fulfillReference を導入しました。
packages/react-server/src/ReactFlightReplyServer.js
createModelResolver 関数では、検証を行わずにパスを辿り、そのまま親オブジェクトへ代入していました。
function createModelResolver<T>(
chunk: SomeChunk<T>, // 対象のチャンク(非同期で解決される単位)
parentObject: Object, // 結果を割り当てる親オブジェクト
key: string, // 割り当て先のキー名
cyclic: boolean, // 循環参照の可能性があるか
response: Response,
map: (response: Response, model: any) => T,
path: Array<string>, // 参照パス("id:prop:sub" のような構造)
): (value: any) => void {
let blocked;
// ...(初期化処理省略)...
return value => {
// 🚨 問題点: 単純に path に沿ってドリルダウンしている
for (let i = 1; i < path.length; i++) {
value = value[path[i]];
}
// 🚨 問題点: 検証なしにそのまま代入している
parentObject[key] = map(response, value);
// ...(後続処理省略)...
};
}
【問題点】
packages/react-server/src/ReactFlightReplyServer.js
fulfillReference 関数を導入し、ReactPromise(チャンク)の状態確認と hasOwnProperty によるガードを追加しました。
function fulfillReference(
response: Response,
reference: InitializationReference, // 参照(ハンドラ・親・キー・マップ関数・パスを含む)
value: any,
): void {
const {handler, parentObject, key, map, path} = reference;
for (let i = 1; i < path.length; i++) {
// 🛡️ 対策1: value がチャンクなら状態を確認し、未解決なら待機リストへ
while (value instanceof ReactPromise) {
const referencedChunk: SomeChunk<any> = value;
switch (referencedChunk.status) {
case RESOLVED_MODEL:
initializeModelChunk(referencedChunk);
break;
}
switch (referencedChunk.status) {
case INITIALIZED: {
value = referencedChunk.value;
continue;
}
case BLOCKED:
case PENDING: {
// 未解決の場合はパスを調整し、待ちリストに登録して処理を中断(return)
path.splice(0, i - 1);
if (referencedChunk.value === null) {
referencedChunk.value = [reference];
} else {
referencedChunk.value.push(reference);
}
// ...(reasonの処理省略)...
return; // 即座に代入せず、解決を待つ
}
default: {
rejectReference(response, reference.handler, referencedChunk.reason);
return;
}
}
}
const name = path[i];
// 🛡️ 対策2: プロパティアクセス前に own-property をチェック
if (typeof value === 'object' && hasOwnProperty.call(value, name)) {
value = value[name];
} else {
// own-property ではない場合は読み飛ばす(プロトタイプ汚染を防ぐ)
value = undefined;
break;
}
}
// 安全に検査済みの値を代入
const mappedValue = map(response, value, parentObject, key);
parentObject[key] = mappedValue;
// ...(後続のストリーム解決処理)...
}
【変更点と効果】
データの復元(Revive)処理においても、プロパティ書き込み時のチェックが強化されています。
// 検証なしで書き込み
if (newValue !== undefined) {
value[key] = newValue;
}
After:
// 🛡️ __proto__ に対する特殊制御と own-property の前提
if (newValue !== undefined || key === '__proto__') {
value[key] = newValue;
}
【変更点】
サーバーだけでなく、クライアント側(react-server-dom-webpack 等)のモジュール読み込みにも修正が入りました。
packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js
export function requireModule(metadata: ClientReference): T {
const moduleExports = parcelRequire(metadata[ID]);
// 🚨 脆弱: 直接プロパティを返している
return moduleExports[metadata.name];
}
After:
import hasOwnProperty from 'shared/hasOwnProperty';
export function requireModule(metadata: ClientReference): T {
const moduleExports = parcelRequire(metadata[ID]);
// 🛡️ 対策: own-property であることを明示的にチェック
if (hasOwnProperty.call(moduleExports, metadata.name)) {
return moduleExports[metadata.name];
}
// 存在しない/プロトタイプ由来の場合は undefined を返す
return (undefined: any);
}
【効果】
もし moduleExports のプロトタイプが汚染されていたとしても、自身のプロパティでない限り無視されるため、攻撃者が仕込んだ偽のエクスポート関数が実行されるのを防ぎます。
multipart/busboy 等を使用するストリーム処理において、例外発生時の挙動が安全側に倒されました。
これにより、攻撃者が不正なストリームデータを送って部分的な初期化状態を作り出し、そこから更なる攻撃を行うリスクを低減しています。
今回のパッチ(React 19.0.1)により、以下の防御層が確立されました。
Flightプロトコルのデシリアライズ処理において、__proto__ や constructor といったプロトタイプチェーン上のプロパティへのアクセスを hasOwnProperty.call で徹底的に排除しました。
非同期データの解決フローを見直し、未解決データを即座に代入せず待機リストへ回すことで、不正な状態での実行を防ぎました。
エラー発生時にストリームを即座に破棄することで、攻撃の足がかりとなる「不安定な状態」を残さないようにしました。
これらの対策により、「外部入力 → 無検査でのプロパティアクセス → プロトタイプ汚染 → RCE」という攻撃チェーンが断ち切られています。
お見積り・ご相談など、お気軽にご相談ください
サイトTOPへ