BitDAO的3.5億美元險些被盜,白帽黑客講述一場驚心動魄拯救行動全程

巴比特
2021-08-18 11:13:43
收藏
一場驚心動魄的白帽黑客拯救行動。

8月17日,區塊鏈投資機構 Paradigm 研究合夥人、著名白帽黑客 samczsun 撰文披露了BitDAO在 SushiSwap IDO 平台MISO進行的荷蘭拍的智能合約存在安全漏洞,多名白帽黑客聯手從眾籌資金池中拯救回 10.9 萬枚 ETH (約合 3.5 億美元)的經過。

原文標題:《正正得負》(Two Rights Might Make A Wrong

撰文:samczsun

編譯:Kyle,巴比特

本文目錄:

1、相遇

2、發現

3、披露

4、準備

5、救援

6、反思

當構建軟體時經常出現的一個常見誤解是,如果系統中的每個組件都經過單獨驗證是安全的,那麼系統本身也是安全的。 這種觀念在 DeFi 項目中被更多地體現了出來。在 DeFi項目開發中,可組合性就是開發者的第二天性。

不幸的是,雖然將兩個安全的組件部分組在一起,在大多數情況下可能是安全的,但這也意味著只需要一個漏洞即可對數百甚至數千名無辜用戶造成嚴重的經濟損失。

今天,我想告訴你我如何發現並幫助修補了一個嚴重的漏洞,這個漏洞會使超過 109,000 ETH(按今天的匯率計算約 3.5 億美元)面臨被盜風險。

一、相遇

上午9:42

當我在推特上注意到 @ivangbi_ 和 @bantg 之間關於 SushiSwap 的 MISO 平台上的一個新代幣銷售項目的討論時,我正隨意瀏覽 Telegram 上的 LobsterDAO 群聊。 我通常會儘量避免在公共場合做出一些戲劇性的事情,但我忍不住在谷歌上快速搜索了一下,看看到底是怎麼回事。

我得到的結果對我來說並不是特別有趣,但我繼續往下搜索,因為我覺得如果我繼續尋找,就會發現這裡有一些有趣的東西。

MISO 平台支持兩種類型的代幣拍賣模式:荷蘭式拍賣和批量拍賣。 而今天說的這個代幣銷售是通過荷蘭式拍賣進行的。 當然,我做的第一件事就是在 Etherscan 上打開該項目的合約地址。

上午9:44

我通過該項目的參與協議快速瀏覽了這起荷蘭式拍賣的合約並檢查了每個有意思的函數。該合約的提交函數(commitEth、commitTokens 和 commitTokensFrom)似乎都已正確實現。拍賣管理函數(setDocument、setList 等)也有適當的訪問控制。

然而,在底部附近,我注意到 initMarket 函數沒有訪問控制,這非常令人擔憂。此外,它調用的 initAuction 函數也不包含訪問控制檢查。

不過,我真的沒想到會發現這麼一個漏洞,因為我沒想到 Sushi 團隊會犯下如此明顯的失誤。果然,initAccessControls 函數驗證了合約尚未初始化。

然而,這時我又有了另一個發現。在滾動瀏覽所有文件時,我注意到了 SafeTransfer 和 BoringBatchable 庫。我對這兩者都很熟悉,並且立即對 BoringBatchable 庫的潛在危險感到震驚。

這裡先解釋一下,BoringBatchable 是一個混合庫,旨在輕鬆地將批處理調用引入任何導入它的合約。 它通過為輸入中提供的每個調用數據對當前合約執行委託調用來實現這一點。

function batch(bytes[] calldata calls, bool revertOnFail) external payable returns (bool[] memory successes, bytes[] memory results) {
successes = new bool[](calls.length);
results = new bytes[](calls.length);
for (uint256 i = 0; i < calls.length; i++) {
(bool success, bytes memory result) = address(this).delegatecall(calls[i]);
require(success || !revertOnFail, _getRevertMsg(result));
successes[i] = success;
results[i] = result;
}
}

看看上面這個函數,它似乎被正確地實現了。 然而,在我腦海的角落裡,有什麼東西在提醒著我。 那時我意識到我過去曾看到過非常相似的東西。

二、發現

上午 9:47

距離今天打印一年多前,我在與 Opyn 團隊的 Zoom視頻通話中,試圖弄清楚如何在遭受毀滅性黑客攻擊後恢復和保護用戶資金。

黑客攻擊手法本身很簡單但很巧妙:它使用一次 ETH 支付來行使多個期權,因為 Opyn 合約在循環中使用了 msg.value 變量。

雖然處理代幣支付涉及每個循環迭代的單獨 transferFrom 調用,但處理 ETH 支付只是檢查 msg.value 是否足夠。 這允許攻擊者多次重複使用相同的 ETH。

回到今天,我意識到我正在看到的是兩個完全相同的漏洞,只是形式不同。 在委託調用中,msg.sender 和 msg.value 被持久化。 這意味著我應該能夠批量調用 commitEth 並在每個commitment中重複使用我的 msg.value,這將允許我在拍賣中能夠免費出價。

上午9:52

我的直覺告訴我這是真實的交易,但我無法在沒有實際驗證的情況下確定。 我迅速打開 Remix 並編寫了一個概念驗證。

令我沮喪的是,我的主網分叉環境之前不久被完全損壞了。 我一定是在倫敦硬分叉期間不小心弄壞了它。 有這麼多資金正處於風險之中,而我卻沒有足夠的時間。 我很快在命令行上拼湊了一個簡陋的主網分叉並測試了我的漏洞。結果跟我想的一樣。

上午10:13

在對外報告這個漏洞風險之前,我給我的同事 Georgios Konstantopoulos 打了電話,讓他們再看一遍。在等待回應的同時,我又回到合約中尋找確定嚴重性的方法。在這種情況下,能夠免費參加拍賣是一回事,但能夠竊取所有其他參與者的出價則是另一回事。

我注意到在我最初的掃描過程中有一些退款邏輯,但當時我並未多想。現在,這已是一種讓 ETH 退出合約的方法。我很快檢查了我需要滿足哪些條件才能讓合約為我提供退款。

令我驚訝(和恐懼)的是,我發現發送的任何超過拍賣硬上限的 ETH 都會獲得退款。即使達到硬上限,這也適用,這意味著合約不會完全拒絕交易,而是簡單地退還您的所有 ETH。

突然間,我發現的這個漏洞變得巨大。我不是在處理一個讓你出價可以超過其他參與者的漏洞。我在看的是一個價值 3.5 億美元的漏洞。

三、披露

上午10:38

在與 Georgios 確認這個漏洞後,我讓他和 Dan Robinson 嘗試聯繫 Sushi CTO Joseph Delong。 幾分鐘後,Joseph 做出了回應,然後我與 Georgios、Joseph、Mudit、Keno 和 Omakase 一起進行了Zoom 通話。 我就漏洞向其他參與者進行了快速匯報,然後他們開始四處協調響應。 整個通話只持續了幾分鐘。

四、準備

上午11:26

在救援行動室裡,Mudit, Keno, Georgios和我正在忙著寫一份簡單的救援合約。 我們決定最乾淨的做法是發起一筆閃電貸,直接購買到硬上限,結束拍賣,然後使用拍賣本身的收益償還閃電貸。 這種方法不需要前期準備資金,效果非常好。

下午1:36

當我們完成救援合約的工作時,我們討論了批量拍賣的後續步驟。 Mudit 指出,即使在拍賣進行時也可以設置一個積分列表,並且在每次 ETH commitment期間都會調用它。我們立即意識到這可能是我們正在尋找的暫停功能。

我們集思廣益,想出了不同的方法來使用這個方法。立即還原是一個顯而易見的解決方案,但我們想要更好的方案。

我考慮添加一個檢查,每個源只能為每個區塊做出一個commitment,但我們注意到該函數被標記為視圖,這意味著 Solidity 編譯器將使用靜態調用操作碼。我們的方式不允許進行任何狀態修改。

經過一番思考,我意識到我們可以使用積分列表來驗證拍賣合約是否有足夠的 ETH 來匹配所做的commitment。換句話說,如果有人試圖利用這個漏洞,那麼commitment會比 ETH 多。我們可以很容易地檢測到這一點並還原交易。 Mudit 和 Keno 開始編寫測試以進行驗證。

五、救援

下午2:01

通信突圍團隊與救援突圍團隊合併工作以同步進度。 他們已經與執行拍賣的團隊(BitDAO)取得了聯繫,但該團隊希望手動完成拍賣。 我們討論了風險並認為某個自動化機器人注意到這筆交易或能夠對其採取任何行動的可能性很小。

下午 2:44

執行拍賣的團隊完成了拍賣,消除了直接威脅。 我們互相祝賀成功,然後各自解散。 這次批量拍賣將在當天晚些時候悄悄結束。 不知情的人恐怕不知道剛剛避免了一場多麼嚴重的災難。

六、反思

下午4:03

過去的幾個小時讓人感覺很模糊,時間好像靜止一樣。我從相遇這個項目到發現漏洞只用了半個多小時,20 分鐘內進行了披露,另外 30 分鐘內作戰室,三個小時內修復漏洞。總而言之,只用了五個小時就保護了 3.5 億美元不落入壞人之手。

即使沒有金錢上的損失,我相信所有參與其中的人都更願意一開始就沒有經歷過這個過程。針對這次事件,我有兩個主要的要點給你。

首先,在複雜系統中使用 msg.value 很困難。它是一個全局變量,您無法更改並在委託調用中保持不變。如果您使用 msg.value 來檢查是否已收到付款,則絕對不能將該邏輯置於循環中。

隨著代碼庫複雜性的增加,很容易忘記發生的位置並意外地在錯誤的位置循環某些內容。雖然封裝和釋放 ETH 很麻煩並且引入了額外的步驟,但如果想要避免這樣的事情,那麼WETH 和其他 ERC20 代幣之間的統一接口可能值得一試。

其次,兩個安全組件組合在一起,可能就會得到不安全的東西。我之前曾在可組合性和 DeFi 協議的背景下聲明過這一點,但這次事件表明,即使是安全的合約級組件也可能以產生不安全的合約級行為的方式混合。這裡沒有像"檢查-效果-交互"這樣的包羅萬象的建議,所以你只需要了解新組件引入的額外交互。

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

鏈捕手ChainCatcher提醒,請廣大讀者理性看待區塊鏈,切實提高風險意識,警惕各類虛擬代幣發行與炒作,站內所有內容僅係市場信息或相關方觀點,不構成任何形式投資建議。如發現站內內容含敏感信息,可點擊“舉報”,我們會及時處理。
ChainCatcher 與創新者共建Web3世界