5月8日、Lachlan氏が「The React2Shell Story」と題した記事を公開した。この記事では、Reactに発見された重大な脆弱性「React2Shell」の発見から開示まで約1週間の詳細な開発ストーリーについて詳しく紹介されている。以下に、その内容を紹介する。
React2Shellとは何か
React2Shell(CVE-2025-55182)は、2024年11月30日にMetaに報告されたリモートコード実行の脆弱性である。Metaは12月3日に修正版をリリースし、開発者に即座のアップデートを促した。この脆弱性は数百万のWebサイトに影響を与える重大なものだった。
興味深いのは、Lachlan氏が最初からReactの脆弱性を探していたわけではない点だ。現代のWebアプリケーションをより効果的にハッキングするために、あるプロトコルを理解しようとしていた際に偶然発見したのである。
発見の発端:Flightプロトコルへの好奇心
Monday - 執念の始まり
11月24日月曜日、プロフェッショナルハッカーとして普通の業務をこなしていたLachlan氏だったが、午後になって「頭の中でスイッチが切り替わった」と表現するほどの強烈な好奇心に駆られた。
近年、彼はNext.js(Reactベースの人気フレームワーク)で構築された多くのWebアプリケーションをペネトレーションテストしてきた。Next.jsはReact Server Components(RSC)とReact Server Functionsを使用して、サーバーとブラウザ間で効率的にメッセージをやり取りする。
このとき目にした奇妙なリクエスト形式が気になって仕方なくなった:
0=[{ "a": "$$undefined", "b": "$1:foo:bar" }]&1=...
プロトコルの謎
業界全体で「JSONに装飾を加えただけのもの」として軽視されがちだったこの形式について、Lachlan氏は「このプロトコルがより多くのユニークな攻撃手法を提供してくれるなら、絶対に知る必要がある」と感じた。
しかし、調べ始めて判明したのは仕様書が存在しないという事実だった。このプロトコルが「Flight」と呼ばれることを突き止めるのさえ困難で、Xでの数少ないスレッドが唯一の情報源だった。
あるX投稿では「no docs, only code(文書はない、コードのみ)」と書かれており、これがLachlan氏の探究心に「油を注いだ」結果となった。
Flightプロトコルの仕組み
Flightは、クライアントサイドとサーバーサイド間で複雑なJavaScriptオブジェクトをやり取りするためのプロトコルだ。単純なJSONでは表現できないDate、BigInt、Mapなどの複雑なデータ型や、循環参照、Promiseなどをサポートする。
メッセージは「チャンク」に分割され、特別な$構文でFlightの型を示す:
0 = {
"email": "[email protected]",
"updated": "$D04 Dec 1995 00:12:00 GMT",
"details": "$1"
}
&1 = {
"firstName": "$2",
"lastName": "$3:foo"
}
Tuesday - 致命的な発見
プロトタイプチェーンの悪用
Flightにはオブジェクトのプロパティを参照する機能があるが、直接のプロパティだけでなくプロトタイプチェーン上のプロパティも参照できてしまうという問題があった。
0 = {
"foo": "$1:toString"
}
&1 = 123
このメッセージは、攻撃者の制御可能なオブジェクト上にNumber.prototype.toStringを配置できてしまう。
Next.js創設者のGuillermo Rauch氏は後にこれを「安全性チェックの明らかな欠如」と表現した。
TypeScriptの錯覚
多くの開発者は以下のようなコードを「安全」だと考える:
async function sayHello(name: string): string {
'use server'
return 'Hello, ' + name + '!'
}
しかし、TypeScriptの型注釈は実行時には強制されない。攻撃者は悪意のあるtoString関数を持つカスタムオブジェクトを送信でき、文字列連結時にその関数が実行されてしまう。
Wednesday - 執拗な探求
Lachlan氏は同僚のSylvie Mayer氏と協力して攻撃手法の開発を続けた。しかし、興味深い現象が起きていた:
「これは明らかにバグだ → でも、Reactにバグがあるはずがない」
この思考ループに二人とも陥っていたのである。Reactの優秀なセキュリティ実績が、脆弱性発見の可能性を過小評価させていた。
Thursday - 決定的な突破口
木曜日の夜、React2Shellの核心となる発見があった。
FlightペイロードをデコードするReactのdecodeReply関数が、thenable(.then()可能なオブジェクト)を予期せず実行してしまう問題である。
JavaScriptのawaitは内部でPromise.resolve()を呼び出すが、これは正規のPromiseだけでなく、.thenメソッドを持つあらゆるオブジェクト(thenable)も処理してしまう:
> await { then: console.log }
function (), function ()
最終的な攻撃手法
この仕組みを悪用することで、攻撃者は以下を実現できる:
- Flightメッセージで悪意のあるthenableオブジェクトを構築
- そのオブジェクトがReactによって
awaitされる - 任意のコードが実行される
具体的には、Functionコンストラクタを使用して動的にコードを生成・実行する手法が確立された。
業界への影響
React2Shellは単なる技術的な脆弱性にとどまらない。数百万のWebサイトに影響し、Next.jsアプリケーションに対する新たな攻撃ベクトルを明らかにした。
Hacker Newsでは以下のような反応が見られた:
「TypeScriptの型安全性への過信が問題を悪化させている。実行時検証は必須だ」
「Flightプロトコルの仕様が不透明だったことも、この問題を見逃す要因になった」
まとめ
React2Shellの発見は、好奇心から始まった1週間の集中的な研究の成果である。最初はWebアプリケーションの新しい攻撃手法を探していただけだったが、最終的にはReact自体の重大な脆弱性発見につながった。
この事例は、セキュリティ研究者にとって以下の教訓を提供している:
- 文書化されていないプロトコルほど、隠れた攻撃面が存在する可能性が高い
- 著名なフレームワークでも、複雑な機能には予期せぬ脆弱性が潜んでいる
- TypeScriptの型システムは実行時の安全性を保証しない
詳細はThe React2Shell Storyを参照していただきたい。