資安雷達 2026 年 10 月 6 日

2026-10-06 — vm2 沙箱逃逸:循環 AggregateError 繞過清洗達成 RCE

primary=https://github.com/advisories/GHSA-x965-fc75-jpqh primary=https://github.com/patriksimek/vm2/commit/c8c232530b860cfecf6f94bc8d0d0890aa381460

vm2 沙箱再被突破:循環引用的 AggregateError 繞過清洗,直通主機 RCE

GitHub Advisory Database · 2026-10-05

vm2 針對 Error.cause 加上的主機參照清洗,漏掉了「同一個主機 AggregateError 在一次走訪中被重複遇到」的情況,沙箱內的程式碼因此拿到還活著的主機 proxy,可以直接呼叫 child_process。這是 GHSA-m283-3h24-438v 修補的不完整繞過,編號 CVE-2026-92934,CVSS v4 評分 9.5(Critical),修補版本為 3.11.8。

通報者為 zx(Jace),公告於 2026-10-05 在 GitHub Advisory Database 收錄並完成審核。

漏洞機制

vm2 讓主機端函式丟出例外時,會把例外送進 lib/setup-sandbox.js 的 handleException,在交給沙箱前先清掉其中的主機物件參照。為了避免無限遞迴,它用一個 visited 的 WeakMap 記錄走過的物件,遇到已走過的物件就直接回傳原物件,也就是公告所指的第 1819 行。

if (apply(localWeakMapGet, visited, [e])) return e;

對一般 Error 來說這樣做是安全的,因為第一次走訪時 sanitizeErrorCause 與 sanitizeHostOwnProps 已經把主機物件就地封死。但 AggregateError 走的是另一條路:sanitizeAggregateError(約 1954 至 1972 行)會把主機端的實例重建成新的 LocalAggregateError,原物件並沒有被封死。

於是只要同一個主機 AggregateError 在同一次走訪中被遇到第二次,第 1819 行就會把仍然有效的原始主機 proxy 交回去,重建流程再把它塞進「已清洗」的 errors[]。公告列出的觸發形狀有四種:自我循環(agg.errors=[agg])、同一物件在陣列中出現兩次、兩個物件互相參照,以及巢狀的互相或重複參照。

攻擊鏈與重現

前提是嵌入 vm2 的主機程式暴露了一個會丟出例外的函式給沙箱呼叫,而這個例外是帶有主機參照(例如 process)的 AggregateError。公告給的 PoC 如下,節錄關鍵幾行:

const shared = new AggregateError([], 'shared');
shared.leak = process;
throw new AggregateError([shared, shared], 'all failed');
// 沙箱內:
e.errors[1].leak.mainModule.require('child_process').execSync('id')

公告表示在 node v22.23、vm2 3.11.6 上執行後回傳了真實的 id 輸出。它也特別對照了被擋下的情況:原本的 new Error('x',{cause:process})、一般 Error 的自有屬性、以及沒有循環的 AggregateError,全部都被攔截,只有「被重複走訪」的那一類會漏。這說明修補本身是有效的,只是輸入形狀躲過了它。

公告另外說明,SuppressedError 的類似變體無法重現,因此不在這則 CVE 的範圍內。

受影響版本

  • 公告頁面的受影響欄位寫「<= 3.11.7」,修補版本為 3.11.8。
  • 公告內文的「Affected Versions」段落寫「<= 3.11.6」,並說明漏洞從引入清洗的 7e3faaf(tag 3.11.6)之後才存在。兩處寫法不一致,實務上以頁面欄位為準,直接升到 3.11.8 即可。
  • 主機端必須有暴露給沙箱、且可能丟出帶主機參照例外的函式,才構成入口。

影響範圍

這個洞影響把 vm2 當作隔離邊界的服務:執行使用者提交腳本的工作流程引擎、低程式碼平台、外掛或公式執行器、AI agent 的程式碼工具等。一旦逃逸,攻擊者取得的是主機行程的權限,公告也提到可讀取 process.env 與 .pid,環境變數裡的金鑰與憑證會一併外洩。

要檢查的是兩件事。第一,專案相依樹中是否有 vm2,包含間接相依,可用 npm ls vm2 確認。第二,傳給 new VM({ sandbox }) 或 NodeVM 的主機函式,是否可能丟出帶有 process、模組或其他主機物件參照的例外,這類函式在升級前最容易成為入口。

修補與緩解

升級到 3.11.8,修補提交為 c8c2325。公告提出的修法有兩種:在循環短路處改回傳 visited 中已記下的沙箱端替代物件,而不是原始主機物件;或在遞迴進入子錯誤之前,先把主機端的 AggregateError 就地封死,與一般 Error 的做法一致。公告沒有說明 3.11.8 實際採用哪一種,release 頁面也未提供可引用的說明。

無法立刻升級的話,公告沒有提供其他緩解方式。保守的做法是讓暴露給沙箱的主機函式一律捕捉內部例外,只丟出純字串或不含任何主機參照的新 Error,避免直接傳遞外部產生的例外物件。這是從機制推導出的降低風險做法,並非公告的建議。

原始來源:GitHub Advisory GHSA-x965-fc75-jpqh、vm2 修補提交 c8c2325


End of article
0
Would love your thoughts, please comment.x
()
x