正正可以得負—— MISO 漏洞反映出的組件疊加問題

摘要:原標題:Two Rights Might Make A Wrong 來源:paradigm 作者:samczsun 編譯、整理:Chen Zou 許多工程師都認為如果一個系統中的每個組件都被單獨驗證為安全的,那麼這個系統本身也是安全的——這是構建軟件的一個常見誤區。這個概念在DeFi中得到了很好的詮…

原標題:Two Rights Might Make A Wrong

來源:paradigm

作者:samczsun

編譯、整理:Chen Zou

許多工程師都認為如果一個系統中的每個組件都被單獨驗證為安全的,那麼這個系統本身也是安全的——這是構建軟件的一個常見誤區。這個概念在DeFi中得到了很好的詮釋,在那裡,可組合性是開發者的第二天性。不幸的是,雖然兩個組件的組合在大多數情況下可能是安全的,但只需要一個簡單的漏洞就可以對數百甚至數千名無辜的用戶造成嚴重的經濟損失。今天,我想告訴你們,我是如何發現並幫助修補一個漏洞,而這個漏洞曾使超過10.9萬枚以太坊(以今天的匯率計算約3.5億美元)處於危險之中。

我在不經意間瀏覽Telegram上的LobsterDAO群時,注意到@ivangbi_和@bantg之間關於壽司互換味噌平台上的一個新加價的討論。我通常會盡量避免在公共場合進行討論,但我忍不住在谷歌上快速搜索,看看這到底是怎麼回事。但我並沒有得到什麼能激起我興趣的答案,於是我繼續順藤摸瓜,因為我覺得如果我繼續尋找的話,最後總能發現一些有趣的東西。

MISO平台運營兩種類型的拍賣。荷蘭式拍賣和批量拍賣。在這種情況下,加價是通過荷蘭式拍賣進行的。自然,我做的第一件事就是在Etherscan上打開該智能合約。

我根據參與協議快速瀏覽了DutchAuction合約,並檢查了每個有趣的功能。提交功能(commitEth、commitTokens和commitTokensFrom)似乎都被正確實現了。拍賣管理函數(setDocument、setList等)也有適當的訪問控制。然而,在接近底部的地方,我注意到initMarket函數沒有訪問控制,這就是個問題了。此外,它所調用的initAuction函數也不包含訪問控制檢查。

不過我開始並不認為這是個漏洞,因為Sushi團隊顯然不會犯下如此明顯的錯誤。果然,initAccessControls函數驗證了合同還沒有被初始化。

然而,這讓我有了另一個發現。在滾動瀏覽所有的文件時,我注意到SafeTransfer和BoringBatchable庫。我對這兩個庫都很熟悉,而且我立即被BoringBatchable庫所帶來的潛力所震撼。

對於那些不熟悉的人來說,BoringBatchable是一個混合庫,它被設計成可以輕鬆地將批量調用引入到任何導入它的合同中。它通過對輸入的每個調用數據在當前合約上執行一個委託調用來實現。

功能批處理(字節[] calldata 調用, bool revertOnFail) 外部應付回報 (bool[] 內存成功,字節[] 記憶結果){

成功 = 新布爾值[](電話。長度)

結果 = 新字節[](電話。長度)

for (uint256 i = 0; i < call.length; i++) {

(bool 成功,字節內存結果) = address(this).delegatecall(calls)[i])

請求 (成功 || !revertOnFail, _getRevertMsg(result))

成功[i] = 成功;

結果[i] = 結果

}

}

看著這個函數,它似乎也被正確地實現了。然而,我腦海中的某個角落卻在嘮叨著。這時我意識到我在過去曾見過非常類似的東西。

發掘線索

上午9點47分

一年多以前的今天,我和奧平團隊在Zoom通話,試圖找出在一次破壞性的黑客攻擊後如何恢復和保護用戶資金。那次攻擊本身很簡單,但卻很聰明:因為Opyn合約在一個循環中使用msg.value變量,所以它用一筆以太坊付款行使了多個期權。當處理代幣支付時,每個循環迭代都需要單獨調用transferFrom,而處理以太坊支付時只需檢查msg.value是否足夠。這使得攻擊者可以多次重複使用同一個以太坊。

我意識到,我看到的是以不同形式存在,但內核完全相同的漏洞。在delegatecall裡面,msg.sender和msg.value被永久化了。這意味著我應該可以多次批量調用commitEth,並在每次承諾中重複使用msg.value,從而允許我在拍賣中免費出價。

上午9點52分

我的直覺告訴我這才是真正的問題,但我在沒有完全驗證它之前依舊無法確定。我迅速打開了Remix,寫了一個概念驗證。但麻煩又來了,我的主網分叉環境完全被破壞了。我一定是在弄倫敦硬分叉的時候不小心破壞了它。但有這麼多的錢處於風險之中,我顧不上修復主網分叉環境了,只能迅速地在命令行上組裝了一個丐版主網分叉,並測試了我的漏洞。顯然,它成功了。

上午10點13分

我聯繫了我的同事Georgios Konstantopoulos,希望在報告之前得到第二雙眼睛的關注。在等待答复的同時,我又回到合同中,尋找提高嚴重程度的方法。能夠免費參與拍賣是一回事,但如果能夠把其他所有的投標也偷走,那就是另一回事了。

在我最初的掃描中,我注意到有一些退款邏輯,但沒有想到。現在,這是一個將ETH從合約中取出的方法。我迅速檢查了我需要滿足哪些條件才能讓合同向我提供退款。

令我驚訝(和恐懼)的是,我發現如果發送的ETH超過了拍賣的硬上限,就會被退款。這甚至在達到硬上限後也適用,這意味著合同不是完全拒絕交易,而是簡單地退還你所有的ETH。

突然間,我的小漏洞變得大了許多。我面對的不是一個可以讓你出價超過其他參與者的漏洞。我看到的是一個價值3.5億美元的漏洞。

摘要

上午10點38分

在與Georgios核實了這個漏洞後,我讓他和Dan Robinson嘗試聯繫Sushi公司的Joseph Delong。幾分鐘內,Joseph就做出了回應,接著我就和Georgios、Joseph、Mudit、Keno和Omakase連線Zoom。我迅速向與會者匯報了這一漏洞,他們就離開了,以協調回應。整個通話只持續了幾分鐘。

上午11點10分

Joseph給Georgios和我回了一個Google Meet的房間。我加入時,Georgios正在向Joseph、Mudit、Keno和Omakase,以及Immunefi的Duncan和Mitchell匯報情況。我們很快就討論了下一步行動。

我們有三個選擇。

1.不管這個漏洞,希望沒有人注意到

2.使用漏洞拯救資金,可能使用Flashbots來隱藏交易

3.通過購買剩餘的分配,並立即完成拍賣來拯救資金,這需要管理員的權限。

經過一些快速的辯論,我們決定選項3是最乾淨的方法。我們分成不同的房間,以便分別進行通信和行動的工作。

準備工作

上午11點26分

在行動室裡,Mudit、Keno、Georgios和我正忙著寫一份簡單的救援合同。我們決定,最乾淨的做法是採取閃電貸款,購買到硬上限,最後完成拍賣,然後用拍賣本身的收益來償還閃電貸款。這就不需要預付資金,這非常好。

上午12點36分

我們遇到了一個問題。原本應該是一個簡單的救援行動,現在卻變成了一顆無法拆除的定時炸彈,因為還有另一個活躍的拍賣。這是一次批量拍賣,這意味著我們不能只買到硬上限,因為根本就沒有硬上限。幸運的是,沒有硬上限也意味著沒有辦法從合同中抽走以太坊,因為該限制下沒有退款渠道。

我們迅速討論了對第一份合約進行白帽救援的利弊,並最終決定,即使批量拍賣有800萬美元的承諾,這800萬美元也沒有風險,而原始荷蘭拍賣中的3.5億美元仍有很大風險。即使有人因為我們強制停止荷蘭拍賣而被影響,並在批量拍賣中發現了錯誤,我們仍然可以保住大部分的資金。於是團隊選擇繼續進行救援。

下午1點36分

當我們結束了救援合同的工作時,我們討論了批量拍賣的下一步工作。 Mudit指出,有一個積分列表,即使在拍賣過程中也可以設置,並且在每個ETH承諾期間都會調用。我們立即意識到這可能是我們正在尋找的暫停功能。

我們集思廣益,用不同的方法來利用這個鉤子。立即恢復是一個明顯的解決方案,但我們想要更好的東西。我考慮添加一個檢查點,即每個原點在每個塊中只能做一個承諾,但我們注意到該函數被標記為視圖,這意味著Solidity編譯器會使用靜態調用操作碼。我們的鉤子將不會被允許做任何狀態修改。

經過一番思考,我意識到我們可以使用積分列表來驗證拍賣合約是否有足夠的以太坊來匹配所做的承諾。換句話說,如果有人試圖利用這個錯誤,那麼他就需要更多以太坊來進行驗證。我們可以很容易地檢測到這一點並恢復交易。 Mudit和Keno開始著手寫一個測試來驗證。

拯救行動

下午2點01分

通訊小組與行動小組合併,以同步進展。他們已經與執行加價的團隊取得了聯繫,但該團隊想手動完成拍賣。我們討論了風險,並同意自動機器人注意到該交易或能夠採取任何行動的可能性很小。

下午2點44分

執行加價的團隊最終完成了拍賣,解除了眼前的威脅。我們互相祝賀,然後各奔東西。這批拍賣會將在當天晚些時候完成,沒有什麼大張旗鼓宣傳。圈外的人都不知道我們剛剛避免了怎樣的一場危機。

反思

下午4點03分

過去的幾個小時感覺很模糊,幾乎就像沒有時間過去一樣。我從偶遇該項目到發現問題只用了半個多小時,披露用了20分鐘,組建行動室又用了30分鐘,修復漏洞用了三個小時。總而言之,只用了五個小時就保護了3.5億美元不落入壞人手中。

儘管沒有金錢上的損失,但我相信每個人都希望一開始就不要經歷這個過程。為此,我有兩個主要的啟示給你們。

首先,在復雜的系統中使用msg.value是困難的。它是一個全局變量,你不能改變,而且在不同的委託調用中都會持續存在。如果您使用msg.value來檢查是否收到付款,您絕對不能把這個邏輯放在一個循環中。隨著代碼庫的複雜度增加,很容易就會它發生的位置,並意外地在錯誤的地方進行循環。雖然對以太坊的打包和解包很繁瑣,並引入了額外的步驟,但如果WETH和其他ERC20代幣之間的統一接口能避免這樣的事情發生,那麼這個成本可能是非常值得的。

第二,安全的組件可以組合在一起,形成不安全的東西。我以前曾在可組合性和DeFi協議的背景下宣揚過這一點,但這次事件表明,即使是安全的合約級組件也可以混合在一起,產生不安全的合約級漏洞。這裡沒有像”檢查-效果-互動”那樣的萬能建議,所以你必須要認識到新的組件會引入,將會給智能合約帶來哪些額外的互動。

我要感謝Sushi的貢獻者Joseph、Mudit、Keno和Omakase對這個問題極其迅速的反應,以及我的同事Georgios、Dan和Jim在整個過程中的幫助,包括審查這篇文章。

本文來自0x新聞Bitpush.News,轉載需註明出處

Total
0
Shares
Related Posts