摘要

本報告深入技術分析了 Chrome V8 JavaScript 引擎中的型別混淆漏洞(Type confusion vulnerabilities),特別聚焦於 CVE-2025-10585 。型別混淆漏洞是一種關鍵的資安缺陷,起因於程式誤解了物件的型別,導致記憶體毀損並可能觸發任意程式碼執行。我們將深入探討 V8 的 JIT 編譯機制、inline caches 的角色,以及攻擊者如何精心設計惡意的 JavaScript 來顛覆型別假設。本報告也檢視了攻擊程序,包含使用 addrof fakeobj Primitives,並透過程式碼範例和 Mermaid 圖表來說明這些概念。此分析是根據公開資訊和類似漏洞,以提供對這些複雜攻擊的全面性理解。

別讓瀏覽器成為破口:看懂 CVE-2025-10585,V8 型別混淆如何引發遠端程式碼執行 | 資訊安全新聞

1. 簡介

V8 JavaScript 引擎是由 Google 開發的開源元件,對於 Chromium 核心的網頁瀏覽器(如 Google Chrome 和 Microsoft Edge)的效能至關重要。其 Just-In-Time(JIT) 編譯能力,特別是透過 TurboFan 等元件,旨在最佳化 JavaScript 的執行速度。然而,這些最佳化的複雜性可能會引入微小的漏洞,其中最關鍵的就是型別混淆缺陷。型別混淆發生在引擎因為邏輯錯誤或攻擊者的操作,將一塊記憶體視為與其預期不同的資料型別時。這種誤解可能導致嚴重的後果,包括堆積毀損(heap corruption)、資訊洩漏,以及最終在瀏覽器的沙箱內執行任意程式碼 [1]。

本報告重點在於理解 V8 中型別混淆漏洞的技術基礎,並以 CVE-2025-10585 作為主要範例。儘管出於資安政策考量, CVE-2025-10585 的具體概念驗證 (Proof-of-Concept, PoC) 細節通常受到限制,但漏洞利用的原理在類似的 V8 型別混淆錯誤中是共通的。我們將探討這些漏洞如何被觸發、其造成的記憶體影響,以及攻擊者為實現遠端程式碼執行 (RCE) 可能採取的後續步驟,包括建構 exploit primitives 和潛在的沙箱逃脫。我們的目標是提供詳細的技術闡述,著重於程式碼分析和結構圖,以闡明這些複雜的攻擊向量。

2. V8 中型別混淆的技術說明

V8 的架構高度依賴 JIT 編譯來實現高效能。當 JavaScript 程式碼被執行時,V8 的 Ignition 直譯器會先處理它,產生 Bytecode。接著,頻繁執行的程式碼路徑會被傳遞給 TurboFan 等最佳化編譯器。TurboFan 對變數和物件的型別進行激進的假設,以產生高度最佳化的機器碼。這些假設基於早期執行期間收集的型別回饋,這些回饋通常儲存在 Inline Caches (ICs) 中 [1]。

當這些型別假設被顛覆時,就會出現型別混淆漏洞。攻擊者可以精心設計特定的 JavaScript Payload,在執行的關鍵階段(例如,最佳化或物件操作),導致 V8 誤解物件的型別。例如,一個預期為數字的物件可能被視為陣列,反之亦然。這種誤解導致不正確的記憶體存取模式,因為引擎會根據錯誤的物件布局來嘗試讀取或寫入資料。

2.1. 觸發機制

一個常見的觸發機制涉及特製的 JavaScript 物件,例如 Proxy WeakMap 物件,它們可以干擾 V8 的內部型別追蹤。正如對 CVE-2025-10585 的分析所描述,攻擊者可以使用 Proxy 物件來欺騙 V8 的 IC 機制。這可能導致物件被視為意外的型別,例如數字被解讀為陣列 [1]。

graph TD A["User Visit
Malicious HTML Page"] --> B["JavaScript Loading
Crafted Proxy/WeakMap Object"] B --> C["V8 Ignition Parser
AST Generation"] C --> D["TurboFan Optimization
Type Assumption (IC Cache)"] D --> E["Type Confusion Trigger
Incorrect Type Interpretation (e.g., Number → Array)"] E --> F["Memory Corruption
Heap Overflow / Arbitrary Read/Write"] F --> G["ROP Chain Setup
Sandbox Escape (Optional)"] G --> H["RCE: Shellcode Execution
System Access"] style A fill:#e1f5fe style H fill:#ffebee style E fill:#ffeb3b

圖 1: 型別混淆流程圖 [1]

2.2. 記憶體影響

不正確的型別解讀會產生深遠的記憶體影響。當 V8 存取一個型別混淆的物件時,它會使用錯誤的記憶體偏移量來存取屬性或資料。這可能導致:

  • 堆積緩衝區溢位 (Heap Buffer Overflows): 寫入超出物件在堆積上的預期邊界。
  • 釋放後使用 (Use-After-Free, UAF): 存取已經被釋放的記憶體,如果記憶體已被重新分配作其他用途,可能導致資料毀損或 Control flow hijacking。
  • 任意讀寫: 能夠讀取或寫入 V8 堆積內的任何記憶體位置,這是漏洞利用開發的關鍵Primitives。

這些 Memory corruption primitives 操作是攻擊者獲取執行控制流並最終實現 RCE 的基礎。即使在 V8 的沙箱內,此類漏洞也可以與其他缺陷(例如沙箱逃脫)串聯,以實現系統層級的存取 [1]。

3. 程式碼分析:型別混淆的內部運作

理解型別混淆通常需要從低階檢查 V8 如何處理物件型別。雖然針對零時差攻擊的具體 PoC 很少公開,但基於類似 V8 型別混淆的假設性漏洞攻擊 (例如,CVE-2022-1134、CVE-2024-5830) 提供了寶貴的見解 [1, 2]。

3.1. 特製的 JavaScript Payload

攻擊者經常使用 JavaScript 功能,如 Proxy 物件來操縱 V8 的型別回饋。考慮以下來自類似 CVE 的假設性 PoC 片段 [1]:

  1. // Hypothetical PoC Snippet (Adapted from Similar CVEs)
  2. let victim = new Proxy({}, {
  3. get(target, prop) {
  4. if (prop === Symbol.toPrimitive) {
  5. // Type Mismatch: Return array instead of expected number
  6. return () => [1.1, 2.2, 3.3]; // Float array misinterpreted as number
  7. }
  8. return Reflect.get(...arguments);
  9. }
  10. });
  11. // Optimization Trigger Loop
  12. for (let i = 0; i < 10000; i++) {
  13. let x = +victim; // ToNumber() call triggers type confusion
  14. if (x.length) { // Incorrect type: numbers don't have length!
  15. // Memory Read: x[0] enables arbitrary read
  16. console.log(x[0]); // Heap leak
  17. }
  18. }

分析: 在這段程式碼中, +victim 運算子嘗試將 victim 物件轉換為數字,呼叫其 Symbol.toPrimitive 方法。惡意的 Proxy 被設計為在預期數字時回傳一個陣列 ( [1.1, 2.2, 3.3] )。在最佳化觸發迴圈中重複執行後,V8 的 TurboFan 編譯器會假設 victim 永遠會回傳數字,並根據此假設生成最佳化程式碼。當 Proxy 接著回傳一個陣列時,最佳化程式碼會將陣列的記憶體誤解為數字,導致型別混淆。後續的 x.length 檢查,對於數字會是 false 但對於陣列會是 true,這揭示了型別混淆並可用於觸發記憶體毀損,例如透過存取 x[0] 造成堆積洩漏 [1]。

3.2. 反組譯和記憶體視覺化

在機器碼層級,型別混淆表現為不正確的記憶體存取。來自 V8 TurboFan 輸出的偽組譯碼表示可能看起來像這樣 [1]:

  1. ; Pseudo-Assembly (V8 TurboFan Output)
  2. 0x12345678: mov rax, [rdi + 0x10] ; Load object type (expected: Number)
  3. 0x1234567f: cmp rax, #kNumberTag ; Type check
  4. 0x12345686: jne 0x12345700 ; Mismatch → Confusion!
  5. 0x1234568c: mov rax, [rdi + 0x08] ; Incorrect offset: Access array buffer
  6. ; Attacker can perform arbitrary write here

這段偽組譯碼說明了 JIT 編譯的程式碼如何執行型別檢查 ( cmp rax, #kNumberTag )。如果型別發生混淆,這項檢查可能會被繞過或誤解,導致不正確的記憶體存取 ( mov rax, [rdi + 0x08] ),這可能會存取陣列緩衝區而非數字的值,從而啟用任意讀寫功能 [1]。

graph LR A["Heap Object Header
(Map Pointer)"] --> B["Number Value
(Smi Tag: 0x00)"] C["Proxy Handler
(Type: JSProxy)"] --> D["Target Object"] D --> E["Traps (get/set)"] subgraph "Normal Path (Secure)" A --> B end subgraph "Confusion Path (Exploit)" C --> F["Misinterpretation: ArrayBufferView"] F --> G["Arbitrary Read/Write
(OOB Access)"] end style B fill:#e8f5e8 style G fill:#f44336

圖 2: 記憶體布局圖,說明型別混淆 [1]

3.3. 物件轉換與型別混淆 (CVE-2024-5830)

型別混淆的另一個範例涉及物件轉換,如 CVE-2024-5830 [2] 中所示。此漏洞源於 V8 的內部函數 PrepareForDataProperty CreateDataProperty 之間的互動,特別是在使用展開語法 (spread syntax) 進行物件複製時。如果被複製的物件包含一個屬性存取器,這個存取器可以在複製過程中被呼叫。這可能導致被複製物件的 map 變得過時。如果隨後 map 的更新在預期 fast map 時回傳了 dictionary map,型別混淆就會發生,破壞物件 [2]。

  1. var x = {};
  2. x.a0 = 1;
  3. x.__defineGetter__("prop", function() {
  4. let obj = {};
  5. obj.a0 = 1; //<--- obj has same map as y at this point
  6. obj.a0 = 1.5; //<--- map of y becomes deprecated
  7. return 1;
  8. });
  9. var y = {...x};

分析: 在此情境中,當 x 被複製到 y 時, prop 存取器被呼叫。在存取器內部,將 1.5 賦值給 obj.a0 (此時與 y 共享相同的 map) 導致 y 的 map 變得過時。當 CreateDataProperty 後續處理 prop 時,它會呼叫 PrepareForDataProperty 中的 Update 。如果此更新產生了 dictionary map,但系統預期的是 fast map,型別混淆就會導致物件毀損,為任意讀寫Primitives提供了途徑 [2]。

4. exploit primitives:addrof 和 fakeobj

一旦型別混淆漏洞被觸發,攻擊者通常會試圖獲取強大的 exploit primitives 來操縱記憶體。在 V8 漏洞攻擊中,兩個最基本的Primitives是 addrof fakeobj [3]。這些通常是在初始 bug 之後、但在實現完全的任意讀寫能力之前建構的。

4.1. The addrof Primitive

addrof primitive 允許攻擊者獲取任何 JavaScript 物件的記憶體位址。這是關鍵的資訊洩漏。透過知道物件的確切記憶體位置,攻擊者可以了解其內部結構並計算其屬性或 backing stores 的偏移量。這個Primitives通常是透過利用型別混淆,將物件誤解為一個會暴露其內部指標的資料結構來實現的,例如一個可以儲存 raw pointers 的陣列 [3]。

4.2. The fakeobj Primitive

fakeobj primitive 與 addrof 協同運作,使攻擊者能夠在受控的記憶體位址創建一個 fake JavaScript 物件。這是透過操縱物件 metadata 和 pointers 來實現的,有效地欺騙 V8 引擎將一塊特製的記憶體區塊視為一個合法的 JavaScript 物件。有了 fakeobj primitive,攻擊者可以控制這個 fake 物件的屬性和方法,從而實現在 V8 堆積上的任意讀寫功能。例如,透過偽造一個 ArrayBuffer 物件,攻擊者可以控制其 backing store pointer 和 length,使他們能夠讀取或寫入任意記憶體位置 [3]。

4.3. 一般漏洞攻擊流程

在像 V8 這樣的 JavaScript 引擎中,利用型別混淆和這些 Primitives 的典型漏洞攻擊程序通常遵循這些步驟 [3]:

  1. 觸發漏洞: 初始的 bug,例如型別混淆或越界寫入,在瀏覽器的 renderer process 內被觸發。這是漏洞攻擊的切入點。
  2. 建構Primitives: 使用初始漏洞,攻擊者建構 addrof fakeobj Primitives。這一步對於從有限的 bug 過渡到強大的記憶體操縱能力至關重要。
  3. 實現任意讀寫: 有了 addrof fakeobj Primitives,攻擊者獲得了讀取和寫入 V8 堆積內任意記憶體位置的能力。這通常是透過偽造一個 ArrayBuffer 或類似的資料結構來控制其底層記憶體來實現的。
  4. 實現程式碼執行: 一旦建立任意讀寫,攻擊者可以覆蓋關鍵資料結構或函數 pointers,以重新導向程式執行。一種常見的技術是覆蓋 ArrayBuffer 的 backing store pointer,使其指向一個被標記為可執行 (RWX) 的記憶體頁面,然後將 shellcode 寫入該頁面。
  5. 沙箱逃脫: 在現代的 V8 和瀏覽器環境中,在 V8 堆積內實現程式碼執行通常不足以實現完全的系統危害,因為有沙箱。需要進一步的步驟來逃脫 V8 堆積沙箱,然後是更廣泛的 renderer 沙箱,以獲得對底層作業系統的存取權。

5. 結論

Chrome V8 JavaScript 引擎中的型別混淆漏洞對瀏覽器安全構成了重大威脅, CVE-2025-10585 和類似缺陷就是例證。這些漏洞利用了 V8 JIT 編譯器中複雜的最佳化,特別是其型別推斷和 inline caching 機制。透過精心設計惡意的 JavaScript,攻擊者可以顛覆 V8 的型別假設,導致關鍵的記憶體毀損問題,例如堆積緩衝區溢位和任意讀寫能力。隨後的漏洞攻擊鏈通常涉及建構強大的 Primitives,如 addrof fakeobj ,以獲得對記憶體的精細控制,最終為遠端程式碼執行和潛在的沙箱逃脫鋪平道路。此類漏洞的持續發現和修補凸顯了資安研究人員與攻擊者在瀏覽器漏洞攻擊領域中 ongoing cat-and-mouse game。有效的緩解措施取決於及時的修補、JavaScript 引擎內強健的型別驗證,以及多層次的安全防禦,以遏制成功漏洞攻擊的影響。