跳至主要内容

關於 Node.js 的 Hash flooding 漏洞問題…

· 閱讀時間約 6 分鐘
Yang Guo ([@hashseed](https://twitter.com/hashseed))

今年七月初,Node.js 發佈了一個安全更新,針對所有當前維護的分支來解決 Hash flooding 漏洞問題。這項臨時修復以顯著降低啟動性能為代價。同時,V8 已實施了一項解決方案,可以避免性能下降。

本文旨在提供一些關於該漏洞的背景與歷史資訊,以及最終的解決方案。

Hash flooding 攻擊​

Hash 表是計算機科學中最重要的數據結構之一。它被廣泛應用於 V8,例如存儲物件的屬性。通常情況下,插入新項的效率非常高,為𝒪(1)。然而,Hash 碰撞可能導致最壞情況 𝒪(n)。這意味著插入 n 個項可能需要最多 𝒪(n²) 的時間。

在 Node.js 中,HTTP 標頭以 JavaScript 物件的形式表示。標頭名稱和值的對以物件屬性存儲。通過精心準備的 HTTP 請求,攻擊者可能發動拒絕服務攻擊。Node.js 進程會變得無響應,忙於處理最壞情況的 Hash 表插入。

這種攻擊早在 2011 年 12 月就已被披露,並顯示出影響了一系列編程語言。為何 V8 和 Node.js 過了這麼久才解決這個問題呢?

事實上,在漏洞披露後不久,V8 工程師很快與 Node.js 社區合作開發了一個緩解措施。從 Node.js v0.11.8 開始,該問題已經得到解決。這項修復引入了一種名為 Hash 種子值 的機制。Hash 種子在啟動時隨機選取,用以對特定 V8 實例中的每一個 Hash 值進行種子化。在不了解 Hash 種子的情況下,攻擊者很難命中最壞情況,更不用說針對所有 Node.js 實例發動攻擊。

這段修正的提交信息的一部分內容如下:

此版本僅解決對那些自己編譯 V8 或那些不使用快照的人的問題。基於快照的預編譯版 V8 仍將具有可預測的字符串 Hash 值。

此版本僅解決對那些自己編譯 V8 或那些不使用快照的人的問題。基於快照的預編譯版 V8 仍將具有可預測的字符串 Hash 值。

啟動快照​

啟動快照是 V8 中的一種機制,用以顯著加速引擎啟動以及創建新的上下文(即通過 Node.js 中的 vm 模塊)。與其從頭開始設置初始物件和內部數據結構,V8 將從現有的快照進行反序列化。一個基於最新快照構建的 V8 啟動時間少於 3ms,新上下文創建時間僅需幾分之一毫秒。不使用快照的情況下,啟動時間超過 200ms,而新上下文則超過 10ms。這是一個量級上的差異。

我們在上一篇文章中提到了任何 V8 嵌入者如何利用啟動快照。

預構建的快照包含 Hash 表和其他基於 Hash 值的數據結構。一旦從快照初始化後,Hash 種子值便無法改變,否則這些數據結構會遭到破壞。捆綁快照的 Node.js 發佈版有固定的 Hash 種子,導致緩解措施失效。

這就是提交信息中特別警告的原因。

幾乎修復,但仍有問題​

快進到 2015 年,一個 Node.js 的問題報告說創建新上下文的性能有所退化。不出所料,這是因為啟動快照作為緩解措施的一部分被禁用了。然而,當時參與討論的人並不都了解背後的原因。

正如這篇文章所解釋的,V8 使用一個偽隨機數生成器來生成 Math.random 的結果。每個 V8 上下文都擁有自己的隨機數生成器狀態,這樣可以防止 Math.random 的結果在不同的上下文中變得可預測。

隨機數生成器狀態在上下文初始化後立刻從外部來源設置種子。無論上下文是從零開始創建還是從快照反序列化創建,這都無關緊要。

隨機數生成器的狀態不知何故被與雜湊種子混淆。因此,從 io.js v2.0.2 開始,預構建的快照成為官方版本的一部分。

第二次嘗試​

直到 2017 年 5 月,在 V8、Google 的 Project Zero 以及 Google Cloud Platform 之間進行一些內部討論時,才意識到 Node.js 仍然易受到雜湊洪泛攻擊的影響。

初步響應來自我們的同事 Ali 和 Myles,他們隸屬於 Google Cloud Platform 的 Node.js 團隊。他們與 Node.js 社群合作,再次禁用啟動快照。這次,他們還添加了一個測試用例。

但我們並不希望僅僅停留在這裡。禁用啟動快照會對性能產生顯著的影響。多年來,我們在 V8 中添加了許多新的語言 功能 和複雜 優化。其中一些增加使得從零開始的啟動成本更高。在安全版本發布後,我們立即著手尋找長期解決方案,目標是能夠重新啟用啟動快照的同時不會受到雜湊洪泛攻擊的影響。

從提議的解決方案中,我們選擇並實現了最切合實際的一個。從快照反序列化後,我們會選擇一個新的雜湊種子。受影響的數據結構接著進行重新雜湊以確保一致性。

事實證明,在普通的啟動快照中,實際受影響的數據結構很少。令我們欣喜的是,在此期間,重新雜湊雜湊表 在 V8 中已變得非常容易。這增加的開銷微不足道。

重新啟用啟動快照的修補程序已被合併到 Node.js 中。它已成為最近的 Node.js v8.3.0 版本 的一部分。