
此前,CertiK團隊于Sui區(qū)塊鏈發(fā)現(xiàn)了一系列拒絕服務(wù)漏洞。在這些漏洞中,一種新型且具有嚴重影響力的漏洞格外引人注目。該漏洞可導致Sui網(wǎng)絡(luò)節(jié)點無法處理新的交易,效果等同于整個網(wǎng)絡(luò)完全關(guān)閉。
就在上周一,CertiK因發(fā)現(xiàn)該重大安全漏洞,獲得了SUI 50萬美元漏洞賞金。美國業(yè)內(nèi)權(quán)威媒體CoinDesk對該事件進行了報道,隨后各大媒體也緊隨其報道發(fā)布了相關(guān)新聞。
該安全漏洞被形象地稱為“倉鼠輪”:其獨特的攻擊方式與目前已知的攻擊不同,攻擊者只需提交一個大約100字節(jié)的載荷,就能觸發(fā) Sui 驗證節(jié)點中的一個無限循環(huán),使其不能響應(yīng)新的交易。
此外,攻擊帶來的損害在網(wǎng)絡(luò)重啟后仍能持續(xù),并且能在 Sui 網(wǎng)絡(luò)中自動傳播,讓所有節(jié)點如倉鼠在輪上無休止地奔跑一樣無法處理新的交易。因此我們將這種獨特的攻擊類型稱為“倉鼠輪”攻擊。

發(fā)現(xiàn)該漏洞后,CertiK通過Sui的漏洞賞金計劃向Sui進行了報告。Sui也第一時間進行了有效回應(yīng),確認了該漏洞的嚴重性,并在主網(wǎng)啟動前積極采取了相應(yīng)措施對問題進行了修復。除了修復此特定的漏洞外,Sui還實施了預防性的緩解措施,以減少該漏洞可能造成的潛在損害。
為了感謝CertiK團隊負責地披露,Sui向CertiK團隊頒發(fā)了50萬美元獎金。
下文中將從技術(shù)層面披露此關(guān)鍵漏洞的細節(jié),闡明該漏洞的根本原因和潛在影響。

驗證器在Sui中的關(guān)鍵作用
如Sui和Aptos這樣基于Move語言的區(qū)塊鏈,其防止惡意載荷攻擊的保障機制主要是靜態(tài)驗證技術(shù)。通過靜態(tài)驗證技術(shù),Sui可在合約發(fā)布或升級之前檢查用戶提交的載荷有效性。驗證器提供了一系列檢查器用來確保結(jié)構(gòu)和語義的正確性,只有當通過檢查驗證后,合約才會進入Move虛擬機被執(zhí)行。

Move鏈上的惡意載荷威脅
Sui鏈在原始Move虛擬機之上提供了一套新的存儲模型與接口,因此Sui有一個定制版的Move虛擬機。為了支持新的存儲原語,Sui進一步針對不可信載荷的安全驗證引入了一系列額外的、定制的檢查手段,如對象安全及全局存儲訪問等功能。這些定制檢查手段契合了Sui的獨特功能,因此我們將這些定制檢查稱為Sui驗證器。

Sui對載荷的檢查順序
如上圖所示,驗證器中的大多數(shù)檢查會針對 CompiledModule(表示用戶提供的合約載荷運行)進行結(jié)構(gòu)層面的安全驗證。例如,通過“重復檢查器”確保運行時載荷中沒有重復的條目;通過“限制檢查器”確保運行時載荷中每個字段的長度都在允許的條目上限之內(nèi)。
除了結(jié)構(gòu)層面的檢查之外,驗證器的靜態(tài)檢查仍需要更復雜的分析手段,以確保不可信載荷在語義層面的強健性。
了解Move的抽象解釋器:
線性和迭代分析
由Move提供的抽象解釋器,是一個專門為通過抽象解釋在字節(jié)碼上執(zhí)行復雜安全分析而設(shè)計的框架。這種機制使得驗證過程更加精細和準確,每個驗證者都被允許定義他們獨特的抽象狀態(tài)從而進行分析。
在開始運行時,抽象解釋器從編譯的模塊中構(gòu)建控制流圖(CFG)。這些CFG中的每個基本塊都會維護一組狀態(tài),即“前序狀態(tài)”和“后序狀態(tài)”?!扒靶驙顟B(tài)”提供了一個基本塊執(zhí)行前的程序狀態(tài)快照,而“后序狀態(tài)”則提供了基本塊執(zhí)行后的程序狀態(tài)描述。
當抽象解釋器在控制流圖中沒有遇到回跳(或循環(huán))時,它則遵循一個簡單的線性執(zhí)行原則:每個基本塊都被依次分析,并根據(jù)塊中每個指令的語義計算出前序狀態(tài)和后序狀態(tài)。其結(jié)果就是一個程序在執(zhí)行過程中每個基本塊級別狀態(tài)的精準快照,幫助驗證程序的安全屬性。

Move 抽象解釋器的工作流程
然而,當控制流中存在循環(huán)時,這個過程則變得更加復雜。循環(huán)的出現(xiàn)意味著控制流圖中包含一條回跳的邊,回跳邊的源頭對應(yīng)著當前基本塊的后序狀態(tài),而回跳邊的目標基本塊(循環(huán)頭部)是一個之前已經(jīng)分析過的基本塊的前序狀態(tài),因此抽象解釋器需要對回跳相關(guān)的兩個基本塊的狀態(tài)進行仔細合并。
如果發(fā)現(xiàn)合并后狀態(tài)與循環(huán)頭部基本塊現(xiàn)有的前序狀態(tài)不同,抽象解釋器就會更新循環(huán)頭部基本塊的狀態(tài),并從這個基本塊開始重新啟動分析。這個迭代分析過程將一直持續(xù)到循環(huán)預狀態(tài)穩(wěn)定。換句話說,這個過程不斷重復,直到循環(huán)頭部基本塊的前序狀態(tài)在迭代之間不再變化。達到一個固定點,則表明循環(huán)分析已經(jīng)完成。
Sui IDLeak驗證器:
定制的抽象解釋分析
與原來的Move設(shè)計不同,Sui的區(qū)塊鏈平臺引入了一個獨特的以“目標”為中心的全局存儲模型。這個模型的一個顯著特點是:任何具有key屬性(作為索引上鏈存儲)的數(shù)據(jù)結(jié)構(gòu)必須以ID類型作為該結(jié)構(gòu)的第一個字段。ID字段不可改變,且不能轉(zhuǎn)移到其他目標上,因為每個對象必須有一個全局唯一的ID。為了確保這些特性,Sui在抽象解釋器上建立了一套自定義分析邏輯。

IDLeak驗證器,也被稱為id_leak_verifier,與抽象解釋器協(xié)同工作進行分析。它有著自己獨特的AbstractDomain,被稱為AbstractState。每個AbstractState由多個局部變量對應(yīng)的AbstractValue組成。通過AbstractValue來監(jiān)督每個局部變量的狀態(tài),以此來追蹤一個ID變量是否是全新的。
在結(jié)構(gòu)體打包的過程中,IDLeak驗證器只允許將一個全新的ID打包到一個結(jié)構(gòu)體中。通過抽象解釋分析,IDLeak驗證器可以詳盡地跟蹤本地數(shù)據(jù)流狀態(tài),以確保沒有現(xiàn)有的ID被轉(zhuǎn)移到其他結(jié)構(gòu)體對象。
Sui IDLeak驗證器狀態(tài)維護不一致問題
IDLeak驗證器通過實現(xiàn)AbstractState::join函數(shù)與Move抽象解釋器集成。這個函數(shù)在狀態(tài)管理,特別是在合并和更新狀態(tài)值方面中起著不可或缺的作用。
詳細檢查這些函數(shù)以了解它們的操作:

在AbstractState::join中,該函數(shù)將另一個AbstractState作為輸入,并試圖將其本地狀態(tài)與當前對象的本地狀態(tài)合并。對于輸入狀態(tài)中的每個局部變量,它將該變量的值與它在局部狀態(tài)中的當前值進行比較(如果沒有找到,默認值為AbstractValue::Other)。如果這兩個值不相等,它將設(shè)置一個“changed”的標志,作為最終狀態(tài)合并結(jié)果是否變化的依據(jù),并通過調(diào)用AbstractValue::join來更新本地狀態(tài)中的本地變量值。

在AbstractValue::join中,該函數(shù)將其值與另一個AbstractValue進行比較。如果它們相等,它將返回傳入的值。如果不相等,則返回AbstractValue::Other。
然而,這個狀態(tài)維護邏輯包含一個隱藏的不一致性問題。盡管AbstractState::join會基于新舊值的不同而返回一個表示合并狀態(tài)發(fā)生變化(JoinResult::Changed)的結(jié)果,但合并更新后的狀態(tài)值仍然可能是不變的。
這種不一致的問題是由操作順序?qū)е碌?在AbstractState::join中對改變狀態(tài)的判定發(fā)生在狀態(tài)更新(AbstractValue::join)之前,這種判定并不反映真正的狀態(tài)更新結(jié)果。
此外,在AbstractValue::join中,AbstractValue::Other對合并的結(jié)果起著決定性作用。例如,如果舊值是AbstractValue::Other,而新值是AbstractValue::Fresh,則更新的狀態(tài)值仍然是AbstractValue::Other,即便新舊值不同,更新后狀態(tài)本身沒有變化。

示例:狀態(tài)連接的不連貫性
這就引入了一個不一致:即合并基本塊狀態(tài)的結(jié)果被判定為“改變”,但合并后的狀態(tài)值本身并沒有發(fā)生變化。在抽象解釋分析的過程中,出現(xiàn)這種不一致問題有可能產(chǎn)生嚴重的后果。我們回顧抽象解釋器在控制流圖(CFG)中出現(xiàn)循環(huán)時的行為:
當遇到一個循環(huán)時,抽象解釋器采用一種迭代的分析方法來合并回跳目標基本塊和當前基本塊的狀態(tài)。如果合并后的狀態(tài)發(fā)生變化,抽象解釋器則會從跳轉(zhuǎn)目標開始重新分析。
然而,如果抽象解釋分析的合并操作錯誤地將狀態(tài)合并結(jié)果標記為“變化”,而實際上狀態(tài)內(nèi)部變量的值沒有發(fā)生變化,就會導致無休止的重新分析,產(chǎn)生無限循環(huán)。
進一步利用不一致
在Sui IDLeak驗證器中觸發(fā)無限循環(huán)
利用這種不一致性,攻擊者可以構(gòu)造一個惡意的控制流圖,誘使IDLeak驗證器進入一個無限循環(huán)。這個精心構(gòu)造的控制流圖由三個基本塊組成:BB1和BB2,BB3。值得注意的是,我們有意引入了一條從BB3到BB2的回跳邊來構(gòu)造一個循環(huán)。

惡意CFG+狀態(tài),可導致IDLeak驗證器內(nèi)部死循環(huán)
這個過程從BB2開始,其中一個特定局部變量的AbstractValue被設(shè)置為::Other。在執(zhí)行BB2之后,流程轉(zhuǎn)移到BB3,在那里同一變量被設(shè)置為::Fresh。在BB3的結(jié)尾處,有一條回跳邊,跳轉(zhuǎn)到BB2。
在抽象解釋分析這個例子的過程中,前文提到的不一致性起到了關(guān)鍵作用。當回跳邊被處理時,抽象解釋器試圖將BB3的后序狀態(tài)(變量為“::Fresh”)與BB2的前序狀態(tài)(變量為“::Other”)連接起來。AbstractState::join函數(shù)注意到了這個新舊值不同的差異并設(shè)置了“變化”的標志,以此表示需要對BB2的進行重新分析。
然而,AbstractValue::join 中 “::Other”的主導行為意味著AbstractValue合并后,BB2狀態(tài)變量的實際值仍然是“::Other”,狀態(tài)合并的結(jié)果并沒有發(fā)生變化。
因此這個循環(huán)過程一旦開始,即當驗證器繼續(xù)重新分析BB2以及它的所有后繼基本塊節(jié)點(本例中為BB3),它就會無限期地持續(xù)下去。無限循環(huán)消耗了所有可用的CPU周期,使其無法處理響應(yīng)新的交易,這種情況在驗證器重新啟動后仍然存在。
通過利用這個漏洞,驗證節(jié)點如倉鼠在輪上無休止地奔跑一樣無限循環(huán),無法處理新的交易。因此我們將這種獨特的攻擊類型稱為“倉鼠輪”攻擊。
“倉鼠輪”攻擊可以有效地使Sui驗證器陷入停頓,進而導致整個Sui網(wǎng)絡(luò)癱瘓。
理解了漏洞成因與觸發(fā)過程之后,我們通過使用以下Move字節(jié)碼模擬構(gòu)建了一個具體例子,成功地在真實環(huán)境中的模擬中觸發(fā)了該漏洞:

這個例子通過精心構(gòu)造的字節(jié)碼,展示了如何在真實環(huán)境中觸發(fā)漏洞。具體來說,攻擊者可以在IDLeak驗證器中觸發(fā)一個無限循環(huán),利用僅僅約100字節(jié)的載荷即可消耗Sui節(jié)點的所有CPU周期,有效阻止新交易處理,并導致Sui網(wǎng)絡(luò)拒絕服務(wù)。
“倉鼠輪”攻擊在Sui網(wǎng)絡(luò)中的持續(xù)性危害
Sui的漏洞賞金計劃對漏洞等級的評定有著嚴格的規(guī)定,主要依據(jù)對整個網(wǎng)絡(luò)的危害程度進行評定。滿足“嚴重(critical)”評級的漏洞必須使整個網(wǎng)絡(luò)關(guān)停并有效阻礙新交易確認,同時需要硬分叉來修復問題;如果漏洞只能使部分網(wǎng)絡(luò)節(jié)點拒絕服務(wù),至多被評定為 “中危(medium)”或“高危(high)”漏洞。
CertiK Skyfall團隊發(fā)現(xiàn)的“倉鼠輪”漏洞可以使整個Sui網(wǎng)絡(luò)關(guān)停,同時需要官方發(fā)布新版本進行升級修復?;趯υ撀┒吹奈:Τ潭?Sui 最終被將其評定為“嚴重”等級。為了進一步理解“倉鼠輪”攻擊造成的嚴重性影響原因,我們有必要了解Sui后端系統(tǒng)的復雜架構(gòu),特別是鏈上交易發(fā)布或升級的整個過程。

在Sui中提交交易的交互概述
最初,用戶交易通過前端RPC提交,經(jīng)基本驗證后傳遞到后端服務(wù)。Sui后端服務(wù)負責進一步驗證傳入的交易載荷。在成功驗證了用戶的簽名后,交易被轉(zhuǎn)化為交易證書(包含交易信息以及Sui的簽名)。
這些交易證書是Sui網(wǎng)絡(luò)運作的基本組成部分,可以在在網(wǎng)絡(luò)中的各個驗證節(jié)點之間傳播。對于合約創(chuàng)建/升級交易,在其可以上鏈之前,驗證節(jié)點會調(diào)用Sui驗證器檢查并驗證這些證書的合約結(jié)構(gòu)/語義的有效性。正是在這個關(guān)鍵的驗證階段,“死循環(huán)”漏洞可以被觸發(fā)利用。
當該漏洞被觸發(fā)時,它會導致驗證過程無限期中斷,有效阻礙系統(tǒng)處理新交易的能力,并導致網(wǎng)絡(luò)完全關(guān)閉。雪上加霜的是,節(jié)點重啟后該情況仍然存在,這也就意味著傳統(tǒng)的緩解措施遠遠不夠。該漏洞一旦被觸發(fā),則會出現(xiàn)“持續(xù)破壞”的情況從而對整個Sui網(wǎng)絡(luò)留下持久影響。
Sui的解決方法
經(jīng)過CertiK反饋后,Sui及時確認了該漏洞,并發(fā)布了一個修復程序來解決該關(guān)鍵缺陷。該修復程序確保了狀態(tài)改變和改變后標志之間的一致性,消除了“倉鼠輪”攻擊造成的關(guān)鍵影響。

為了消除上述的不一致,Sui的修復包括對AbstractState::join函數(shù)的一個微小但關(guān)鍵的調(diào)整。這個補丁移除了在執(zhí)行AbstractValue::join之前判定狀態(tài)合并結(jié)果的邏輯,取而代之的是首先執(zhí)行AbstractValue::join函數(shù)進行狀態(tài)合并,通過比較最終更新結(jié)果和原始狀態(tài)值(old_value)來設(shè)置合并是否發(fā)生變化的標記。
這樣一來,狀態(tài)合并的結(jié)果與真實更新的結(jié)果將保持一致,分析過程中不會發(fā)生死循環(huán)。
除了修復這個特定的漏洞外,Sui還部署了緩解措施,以減少未來驗證器漏洞的影響。根據(jù)Sui在bug報告中的回復,緩解措施涉及一個叫做Denylist的功能。
“然而,驗證器有一個節(jié)點配置文件,允許他們暫時拒絕某些類別的交易。這個配置可以用來暫時禁止處理發(fā)布和軟件包升級。由于這個bug是在簽署發(fā)布或軟件包升級tx之前運行Sui驗證器時發(fā)生的,而拒絕列表將停止驗證器的運行并將惡意tx丟棄,暫時拒絕列表這些tx類型是一個100%有效的緩解措施(盡管它將暫時中斷試圖發(fā)布或升級代碼的人的服務(wù))。
順便提一下,我們有這個TX拒絕列表配置文件已經(jīng)有一段時間了,但我們也為證書添加了一個類似的機制,作為你之前報告的 “驗證器死循環(huán) ”漏洞的后續(xù)緩解手段。有了這個機制,我們將對這種攻擊有更大的靈活性:我們將使用證書拒絕名單配置來使驗證器忘記壞的證書(打破死循環(huán)),并使用TX拒絕名單配置來禁止發(fā)布/升級,從而防止創(chuàng)建新的惡意攻擊交易。謝謝你讓我們思考這個問題!
驗證器在簽署交易之前有有限的 "ticks"(與gas不同)用于字節(jié)碼驗證,如果在交易中發(fā)布的所有字節(jié)碼不能在這么多ticks中得到驗證,驗證器將拒絕簽署該交易,防止它在網(wǎng)絡(luò)上執(zhí)行。以前,計量只適用于一組選定的復雜驗證器通過。為了應(yīng)對這個問題,我們將計量擴展到每個驗證器,以保證驗證器在每個tick的驗證過程中所執(zhí)行的工作有一個約束。我們還修復了ID泄漏驗證器中的潛在無限循環(huán)錯誤。“
--來自Sui開發(fā)者關(guān)于漏洞修復的說明
總而言之,Denylist使驗證者能夠通過禁用發(fā)布或升級流程來暫時規(guī)避針對驗證器中的漏洞利用并有效地防止一些惡意交易帶來的的潛在破壞。當Denylist的緩解措施生效時,節(jié)點通過犧牲自身的發(fā)布/更新合約功能,來確保自己能夠繼續(xù)工作。

總結(jié)
本文我們分享了由CertiK Skyfall團隊發(fā)現(xiàn)的“倉鼠輪”攻擊技術(shù)細節(jié),解釋了這種新型攻擊是如何利用關(guān)鍵漏洞來導致Sui網(wǎng)絡(luò)完全關(guān)閉的。此外,我們也仔細研究了Sui為修復這一關(guān)鍵問題所做的及時反應(yīng),并分享了漏洞修復以及后續(xù)同類漏洞緩解的方法。
關(guān)注CertiK,獲取更多同類技術(shù)文章及資訊。
