這是什麼意思?

then 注入

Object.prototype.then = function(resolve, reject) {
  console.log('試著生出 then 方法');
  resolve(true);
};

async function dummy() {
  await {};
}

dummy(); // "試著生出 then 方法"

明明只是 await 一個 {} 這種既不是 Promise 也什麼都不是的物件,then 卻不知為何被觸發了。
為什麼???

其實,await 運算子的規格不是「如果引數是 Promise 就解析」,而是「如果引數是 thenable 就解析」。
那麼要怎麼判斷是不是 thenable 呢?答案是:「有 then 方法」。
真的假的。

之所以會有這種規格,是為了相容很久以前在 JavaScript 規格還沒有 Promise 和 async/await 時,各家瀏覽器就已經各自實作的舊行為。

不過,像這種程式碼都能跑的話,總會讓人有很不妙的預感對吧。

沒錯,眾所皆知的 CVE-2025-55182 也就是 React2Shell,就是直接利用這個 then 注入來達成的。

最小 PoC

// FROM trendmicro
{
  0: {
    status: "resolved_model",
    reason: -1,
    _response: {
      _prefix: "console.log('RCE')//",
      _formData: { get: "$1:then:constructor" },
    },
    then: "$1:then",
    value: '{"then":"$B"}',
  },
  1: "$@0",
}

這段原始碼的細節請參考解說文章,但光看就知道它對 then 方法做了些手腳。
這讓原本不應該出現的物件也能被 resolve,成為在伺服器端執行任意程式碼的跳板。

此外,像 CVE-2024-43357、CVE-2024-9680、CVE-2021-21206 等各種漏洞,也都是因為這個規格而發生。

因此,終於有人提出了一個「乾脆把它修掉吧」的 Proposal。
不過為了維持相容性,並沒有單純改成「如果引數是 Promise 就解析」,而是提出了一個要檢查 then 是從哪裡來、再視情況處理之類的複雜方案,感覺又會找到什麼漏洞的繞路解法。

以下就來介紹這個 Proposal:Curtailing the power of "Thenables" (SafeResolve)。
目前階段是 2.7,規格大致已經定案,正在測試階段。

Curtailing the power of "Thenables" (SafeResolve)

Introduction & Problem

以下引用自 MDN。

JavaScript 生態系中,在 Promise 成為語言一部分之前,就已經有多種 Promise 實作。它們在內部以各種方式表達,但至少所有類 Promise 物件都會實作 Thenable 介面。Thenable 會實作 .then() 方法,這個方法接受兩個 callback,一個在 Promise fulfilled 時呼叫,另一個在 Promise rejected 時呼叫。Promise 本身也是 Thenable。

為了與既有的 Promise 實作互通,語言允許使用 Thenable 代替 Promise。例如,Promise.resolve 不只會處理 Promise 的解析,也會追蹤 Thenable。

我們想處理的問題是,then 的查找會沿著整個原型鏈往上找。
這也包含了 Object.prototype。
當 thenable 要處理的是意料之外的型別時,這就特別危險。

Why is this a problem?

有什麼問題?

最明顯的問題就是資安漏洞。
所有標準功能與 Web 平台,即使是在做相容性支援時,也都必須注意安全性。
如果設計有問題,就更容易被濫用。

・CVE-2024-43357 規格上的問題
・ReadableStream::Close 越界存取
・CVE-2021-21206 Use-After-Free 漏洞
・CVE-2024-9086
・以及其他尚未公開的漏洞

本規格之所以會被視為造成資安漏洞的原因,是因為它額外加入了原本不應該存在的使用者程式碼執行路徑,而且這件事很不容易被注意到。

特別危險的是,開發者把已知型別的新物件當成已知型別的安全物件,然後呼叫 Promise.resolve 的情況。
JavaScript 的物件通常原型都會是 Object,因此對 thenable 會有脆弱性。

這不只是資安問題,也會讓複雜度不必要地增加。
Web Platform Tests 甚至還有只為了規範當 then 被動手腳時行為應該如何的測試。

How do I propose we fix this?

要怎麼修?

本 proposal 導入了 SafeResolve,先檢查解決 Promise 時是否有可能執行使用者程式碼。
如果不會執行使用者程式碼,就維持原樣,直接呼叫 Promise.resolve。
如果有可能執行使用者程式碼,則會另外將一個新的非同步任務排入佇列,來解決該 Promise,並且限制它只能執行一次。

Mozilla 的 DOM 團隊已經把 WebIDL 的 Promise resolve 改成這樣了。
而且未來也打算把所有 Promise resolve 都改成這種方式。
這樣一來,Promise resolve 的 script 就不會在 C++ 執行期間同時跑起來,C++ 程式碼的安全性也會提高。
此外,實作時需要考慮的事項也會更單純。

WebIDL 的討論在 whatwg/webidl #1584。

Is this a bulletproof fix?

這是萬無一失的對策嗎?

不是。

前面列出的項目中,這個對策能修掉的資安漏洞有以下這些。

・ReadableStream::Close
・CVE-2021-21206
・CVE-2024-9086

這個則無法解決。

・CVE-2024-43357

Compatibility

只要牽涉到 thenable,microtask 的執行順序就可能改變。
處理 Promise 的程式通常都會對執行順序有一定的韌性,但還是有可能出現相容性問題。

Prior Art & Related Work

相關研究。

Symbol.thenable

已撤回。
由於變更模組命名空間中的 then 行為與 Web 不相容,而且帶來的效益不足以抵銷導入後的效能下降,因此被認定不值得採用。

Stabilize

嘗試導入通用的不變機制。

Proposal History

・2025 年 2 月 變成 Stage 1
・2025 年 7 月
・2026 年 3 月
・2026 年 5 月
・2026 年 7 月 變成 Stage 2.7

感想

老實說,光看這個 Proposal 完全看不懂,所以我又另外查了一堆資料。

看起來,最一開始提到的 then 觸發 resolve 這件事,本身並不是這個 proposal 的主要問題;真正的問題是,現在這個解析流程會跟瀏覽器內部的 C++ 程式碼同時執行,結果導致 Use-After-Free 或 越界存取 之類的記憶體破壞。
因此解法就是不讓 resolve 立即執行,而是先丟進佇列,等其他處理都結束後再執行。

這樣一來,即使能防住記憶體破壞類漏洞,像單次的原型污染導致的不正當資料送出,應該還是防不了吧?
這部分我也不是很確定,懂的人請教教我。


原文出處:https://qiita.com/rana_kualu/items/4bf77973ab94453a2699


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝59   💬2  
856
🥈
NewsData
2
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登