Deconstructing React2Shell: A Deep Dive into a Critical RCE in React Flight
React2Shell (CVE-2025-55182) 的發現,堪稱是一場由好奇心驅動的安全研究大師課。最初僅僅是想了解一個未公開的協定,最終卻演變成一個影響數百萬個使用 React Server Components (RSC) 和 Server Functions 的網站的關鍵遠端程式碼執行 (RCE) 漏洞。
本文將剖析從最初的好奇心到最終漏洞利用鏈的技術歷程,並強調未公開協定的危險性,以及現代 JavaScript 框架中類型安全性的幻象。
The Mystery of the Flight Protocol
要理解 React2Shell,首先必須理解 "Flight",這是 Next.js 和 React 用於促進 Server Components 和 Server Functions 在客戶端與伺服器之間通訊的專有協定。
與標準 JSON 不同,Flight 允許傳輸複雜的 JavaScript 物件,包括 Date、BigInt、Map 以及循環引用。它透過將訊息拆分為 "chunks" 並使用特殊的 $ 語法來表示類型與引用來達成此目的。例如,$D 表示日期,而 $x 則指向另一個 chunk。
對於安全研究人員而言,主要的警訊在於缺乏文件說明。該協定本質上是一個 "black box",使得開發者與安全稽核人員難以理解其攻擊面。正如一位 Hacker News 使用者所評論的,這種缺乏規範的做法,使得在漏洞披露後,識別入侵指標 (IoC) 變得異常困難。
The First Crack: Prototype Pollution and Type Coercion
該漏洞始於 Flight 在處理屬性引用時「顯著遺漏了安全性檢查」。研究人員發現,Flight 不僅允許引用物件上的屬性,還允許引用其原型 (prototype) 上的屬性。
這為類型強制轉換 (type coercion) 攻擊開啟了大門。在許多 Next.js 應用程式中,開發者使用 TypeScript 來定義 Server Functions 的類型。然而,TypeScript 是一個編譯時工具;它不會在執行時 (runtime) 強制執行類型。
考慮如下函數:
async function sayHello(name: string): string {
'use server'
return 'Hello, ' + name + '!'
}
雖然開發者預期的是一個 string,但攻擊者可以利用 Flight 發送一個自定義物件。如果攻擊者將惡意函數放在該物件的 toString 屬性上,伺服器在進行字串串接時會隱含地呼叫該函數,從而導致非預期的程式碼執行。
The Path to RCE: Abusing "Thenables"
雖然類型強制轉換是一個強大的工具,但要實現完整的 RCE 需要更複雜的鏈條。突破點在於發現了 React 如何處理 "thenables"——即實作了 .then() 方法、模擬 Promise 行為的物件。
在 JavaScript 中,await 關鍵字是非常寬容的;如果它等待的是一個 thenable,它會自動呼叫 .then() 方法。研究人員發現,透過發送一個包含 thenable 的精心構造的 Flight payload,他們可以在 await decodeReply(...) 呼叫期間,強迫伺服器執行攻擊者提供的函數。
這種能力讓研究人員能夠串聯多個函數呼叫,有效地在伺服器的記憶體中建立一個用於執行任意函數的原始工具 (primitive)。
The Final Exploit: Spoofing React Internals
最後邁向 RCE 的關鍵一步涉及針對 React 的內部 Chunk 物件。透過使用 $@x 語法(這會建立一個 chunk 的 promise),研究人員可以強迫 React 執行 Chunk.prototype.then 指向一個攻擊者控制的物件,而非合法的 React chunk。
這使得研究人員能夠偽造 Chunk 的內部狀態,包括伺服器清單 (server manifest)——這是一張告訴 React 哪些 Server Function IDs 對應到哪些模組與函數的映射表。
最初,研究人員嘗試覆蓋伺服器清單以將一個 ID 映射到 Node.js 的 child_process.exec,但遇到了 Webpack 的模組系統障礙。最終,更優雅的 PoC 涉及在 Chunk 程式碼中找到一個 React 呼叫函數且帶有可控參數的位置(特別是在 $Bx 程式碼中處理上傳檔案的 blobs),並在那裡植入惡意函數以供執行。
\n## Lessons Learned and Industry Impact
React2Shell 是一個關鍵漏洞,因為它以一種本質上不安全的方式模糊了客戶端與伺服器程式碼之間的界線。正如一位 Hacker News 的評論者所指出,建立一個全新的協定來在信任與不信任的參與者之間序列化複雜物件,是一種高風險的設計選擇。
Key Takeaways for Developers:
- Never trust runtime types: TypeScript 註解並非執行時驗證。務必始終使用 Zod 或 Joi 等函式庫來顯式地驗證使用者輸入。
- Avoid undocumented protocols: 專有且採取 "security through obscurity"(透過隱晦來達成安全)策略的協定,往往隱藏著攻擊者比防禦者更容易發現的關鍵缺陷。
- Implement Bug Bounty Programs: 快速的分類與修復(在 17 小時內確認)展現了專業安全研究人員的價值。正如社群所言,擁有獎勵計畫可以確保研究人員在攻擊者之前先聯繫你。
儘管漏洞利用的複雜性很高,但其影響是毀滅性的。Meta 與 Vercel 團隊通宵達旦地與 Cloudflare 等 WAF 提供商協作,在公開公告 (CVE-2025-55182) 發布前實施了防禦措施,防止了一場大規模災難。