黑客們回歸「第一性原理」,Curve危機不是一次普通的攻擊事件
原文作者:Jaleel,BlockBeats
原文作者:Jack,BlockBeats
原文作者:
原文作者:
原文編輯:
隨著一場漏洞利用事件的發生,DeFi 行業陷入了一場混亂。 Curve Finance,這個DeFi 行業的巨頭,成為了嚴重「攻擊」的目標,諸如alETH/msETH/pETH 的多個穩定幣池岌岌可危。據不完全統計,該漏洞利用事件已造成Alchemix、JPEG'd、MetronomeDAO、deBridge、Ellipsis 和CRV/ETH 池累計損失5200 萬美元,整個市場的信心被嚴重撼動。@BoxMrChen)和BTX 研究員Derek(@begas_btxcap)。
一級標題
一級標題@StaniKulechov一級標題
Curve 事件是怎麼發生的?
Aave 及Lens 的創始人Stani(
)在社交媒體發表了自己對事件的看法:「這對Curve 和DeFi 來說是一個不幸的挫折。雖然DeFi 是一個可以做出貢獻的開放空間,但要做到絕對正確是很困難的,而且風險很高。在Curve 的案例中,他們在協議級別上做對了。」
Curve 遭遇的漏洞利用事件是最古老,也許是最常見的以太坊智能合約攻擊形式之一,重入攻擊。重入攻擊允許攻擊者反複調用智能合約的某一函數,而不等該函數的前一個調用完成。這樣,他們就可以不斷利用漏洞提款,直至受害合約資金耗盡。
重入鎖與CEI 原則舉個簡單的例子來說明重入攻擊:一家銀行總共擁有10 萬現金。但這家銀行有一個很大的漏洞,每當人們取錢時,銀行工作人員並不立即更新賬戶餘額,而是等到一天結束時才進行核對和更新。這時有人發現了這個漏洞,他在銀行開了一個賬戶,先存入1000 元,然後取出1000 元,過5 分鐘再取出1000 元。由於銀行沒有實時更新余額,系統會在進行核對和更新前認為他賬戶還有1000 元。通過反复操作,最終該用戶取出了銀行里全部的10 萬美元現金。到這天結束時銀行才發現被利用了這個漏洞。重入攻擊的複雜性在於它利用了合約之間相互調用的特性,以及合約自身的邏輯漏洞,通過故意觸發異常和回退功能來實現欺詐行為。攻擊者可以反複利用合約的邏輯漏洞,竊取資金。防止重入攻擊的解決方案也很普遍,提前設置一段針對性的特殊代碼內容進行防護,用這樣的保護機制來確保資金安全,這被稱為重入鎖。
Solidity 為智能合約編程設定了一個「
CEI 原則
」(Check Effects Interactions),能很好地保護函數免受重入攻擊。 CEI 原則的內容包括:
1. 函數組件的調用順序應該是:首先是檢查,其次是對狀態變量的影響,最後是與外部實體的交互。
2. 在與外部實體交互之前,應該先更新所有狀態變量。這被稱為「樂觀記賬」,即在交互真正發生之前就將影響寫入。
3. 檢查應在函數開始處進行,以確保調用實體有調用該函數的權限。
4. 狀態變量應在任何外部調用之前更新,以防重入攻擊。
5. 即使是可信的外部實體,也應該遵循CEI 模式,因為它們可能會將控制流轉移給惡意的第三方。
根據文檔的說法,CEI 原則有助於限制合約的攻擊面,特別是防止重入攻擊。 CEI 原則可以很容易地應用,主要就是按功能代碼的順序,不需要改變任何邏輯。眾所周知的導致以太坊分叉的The DAO 漏洞利用,也是無視「CEI 原則」,而被攻擊者實現了重入攻擊,造成了毀滅性後果。

但被攻擊的Curve 池並沒有遵循這個CEI 原則,原因是Curve 採用的是Vyper 編譯器。作為編譯器的Vyper 代碼漏洞,導致重入鎖失效,使得黑客的重入攻擊成功實現了。
大多數人都知道Solidity,但Solidity 並不是創建智能合約的唯一語言,目前替代Solidity 的流行方案就是Vyper。雖然Vyper 的功能和流行程度不如Solidity,但對於熟悉Python 的開發人員來說是理想的選擇,因為Vyper 能將類Python 的代碼轉譯成以太坊智能合約編程語言。@BoxMrChen根據github 的信息顯示,Vyper 的github 代碼庫貢獻第一的開發者,也是Curve 開發者。這也不難解釋Curve 為什麼採用的是Vyper 而不是Solidity。
Vyper 重入鎖為什麼會失效?

那在這次攻擊事件裡,Vyper 的問題究竟出在哪裡?重入鎖為什麼會失效呢?是因為沒有進行測試嗎? BlockBeats 採訪了智能合約開發者Box 826.eth(

),據他透露,Vyper 重入鎖是做過用例測試的。但失效的原因是,測試用例是結果導向,也就是測試用例也是錯的。
簡而言之,Vyper 重入鎖失效最大的原因在於,編寫測試用例的人是根據結果來編寫的測試用例,而沒有思考過為什麼slot 會莫名其妙跳過1 。
在Box 後續分享出來的這幾段Vyper 代碼中,可以明顯看出問題。當鎖名稱第二次出現的時候,storage_slot 的數量會被覆蓋,也就是說,在ret 中,第一次獲取鎖的slot 為0 ,但是再次有函數使用鎖後,鎖的slot 被加一。編譯後使用錯誤的slot,導致重入鎖無法生效。
左為被攻擊代碼,右為修復後代碼「預計錯誤的測試結果,當然驗證不出錯誤。舉個簡單的例子,現在我們在做計算題, 1+ 1 = 2 ,但給定的標準答案錯了,說1+ 1 = 3 。而這時做題的同學答錯了,回答了1+ 1 = 3 ,但卻和提前給定的標準答案相同,程序就自然沒辦法判定出測試結果出錯。」Box 在和BlockBeats 的採訪中這麼說道。懸了兩年的「達摩克利斯之劍」
在有所記錄的史上第一次重入攻擊事件中,
WETH Attack 韋斯攻擊
的攻擊者,正是為了讓開發者重視重入攻擊而故意製造攻擊的白客,目的是使更多項目免受重入攻擊的可能性。在智能合約的情境下,開發者應該採用不同的觸發機制,例如調用某個狀態改變函數來實現保護。這就要求開發人員在設計合約時充分考慮可能的攻擊場景,採取適當的預防措施。 fubuloubu 為了深入了解Vyper 編輯器,BlockBeats 採訪了BTX 研究員Derek(@begas_btxcap),他表示,對於熟悉Python 的開發人員來說,Vyper 是比Solidity 更理想的選擇,UI 界面更舒服,上手更快。但顯然,一些版本的Vyper 編輯器代碼並沒有經過可靠的第三方審計。甚至有的審計工作,可能是開發者自己完成的。 「傳統IT 行業就不會發生這種事,因為一個新的語言出來後,會有無數的審計公司往死裡找你的漏洞。」
更不用說,能讓一個錯誤堂而皇之的存在兩年之久。
Vyper 貢獻者
也表示:編譯器並沒有像大家想像的那樣受到審查或審計。大多數編譯器都會進行重大且頻繁的更改,這使得審核變得不利。即使有完整的代碼庫審核,在此之後添加的版本越多,它也會過時。審核編譯器並不是一個很好的方式,因為審核最終用戶使用該工俱生成的最終產品(即原始EVM 代碼)更有意義。
所有這些都指向最後一個問題:激勵。也就是說沒有人有動力去尋找編譯器中的關鍵漏洞,尤其是舊版本。 fubuloubu 此前提出過一個提案,提議將通過添加由用戶共同贊助的賞金計劃來幫助改進Vyper,但沒有被通過。@StaniKulechov黑客們正在回歸「第一性原理」
對協議和項目的開發人員而言,這是合約安全開發實踐的又一次鮮活案例。但最重要的是,Curve 事件給了我們所有人一個警示,底層編譯器的安全性問題被嚴重忽略了,回歸「第一性原理」的黑客們在更底層的編譯器上找到了一個完美的切入口。
事後,Aave 及Lens 的創始人Stani(
)也在社交媒體發布了長文表達自己的感想:這次Curve 被攻擊事件意味著,DeFi 風險一直涉及整個底層堆棧、編程語言、EVM 等。這警示了我們應更加謹慎和敏感,尤其是將來使用定制的EVM 和應用程序鏈時。
來自更底層的攻擊
對於編譯器的漏洞,僅通過對合約源碼邏輯的審計,是很難發現的。僅僅是研究版本和版本之間的差異也是一個大工程。需要結合特定編譯器版本與特定的代碼模式共同分析,才能確定智能合約是否受編譯器漏洞的影響。「目前只有兩個編譯器最佳,Vyper 的代碼庫更小,更容易閱讀,對其歷史進行分析的更改也更少,這可能就是黑客從這裡下手的原因,Solidity 的代碼庫要更大一些。」fubuloubu 甚至懷疑國家支持的黑客可能參與這起Curve 攻擊事件:「找到該漏洞需要幾周到幾個月的時間,考慮到投入的資源,這也許是由一個小團體或團隊進行的。」作為加密行業使用最廣泛的編譯語言,Solidity 的安全更是被用戶所牽掛,畢竟如果這次是Solidity 編譯器出現了重入鎖失效的問題,那麼整個DeFi 行業的歷史或許都要被改寫了。
根據Solidity 開發團隊定期發布的
安全預警,在多個不同版本的Solidity 編譯器中也都曾存在過安全漏洞。最近一次的編譯器錯誤記錄是6 月26 日,在調查與使用具有副作用的三元表達式的abi.decode 作為類型參數相關的安全報告時,在Solidity 編譯器的舊代碼生成管道中發現了一個錯誤。舊代碼生成器沒有評估複雜的表達式,如賦值、函數調用或條件,其.selector 正在被訪問。這會導致此類表達式未執行的副作用,因此使用舊管道編譯的合約的行為可能不正確。bugs_by_version.json我們也可以看到Solidity 的
Github 倉庫
中放置著一個文件,上面列出了Solidity 編譯器上一些已知的安全相關的bug。該列表可以追溯到0.3.0 版本,只在此版本之前存在的bug 沒有被列入。這裡,還有另外一個
文件。該文件可用於查詢特定的某個編譯器版本會受哪些bug 影響。
幸運的是,也正是因為Solidity 語言的廣泛應用,以及以太坊基金會在背後的輔助,許多已存在的問題已經被項目和協議在部署的過程中被指出。因此Solidity 比Vyper 更快幾步完成了修改和完善,從這個角度看,這也是Solidity 更規範和更安全的原因之一。
為了幫助Solidity 開發者進行更好的測試,防止發生同樣的事。 UnitasProtocol 聯合創始人SunSec(@ 1 nf 0 s 3 cpt)在Curve 被攻擊事件後,發布了一份DeFiVulnLabs Solidity 安全測試指南,支持47 種漏洞,其中包括漏洞描述、場景、防禦、漏洞代碼、緩解措施以及如何測試。
如何盡可能避免底層攻擊?
在這起Curve 事件上,Box 認為所有開發者得到的啟示是:不要貪圖追隨技術潮流選擇不成熟的方案;不要在不寫測試用例的情況下就認可自己的代碼(Vyper 出問題的幾個版本上,甚至連測試用例都是錯誤的);永遠不要自己批准自己的代碼;有些財富,可能要數年才會被發現;不可升級是對自己的傲慢和對其他人的藐視。
通常開發人員也不會想到這裡面有什麼坑,隨手就選一個版本編譯,可能會忽略版本之間的區別所帶來的風險。即使是小版本升級也可能引入重大變化,這一點在開發去中心化應用程序時尤其重要。
此外,要完善代碼的單元測試用例。大部分編譯器級別的錯誤會導致代碼執行結果不一致, 這很難僅通過代碼review 發現, 但在測試中就可以暴露出來。提高代碼覆蓋率有助於避免這類問題。以及要盡量避免使用內聯彙編、多維數組編解碼等複雜語言特性,除非有明確需求。歷史上大多數Solidity 語言漏洞都與這些高級功能相關。在沒有特殊需求的情況下,開發人員應該避免為了炫技而使用實驗性語言特性。
參考內容:
2.https://github.com/pcaversaccio/reentrancy-attacks/issues/1
3.https://nipunp.medium.com/5-8-21-rari-capital-exploit-timeline-analysis-8beda31cbc1a
4.https://docs.Soliditylang.org/en/v0.8.21/


