本次,我們來講一講運用delegatecall函數時更複雜的合約漏洞案例。

目標合約

智能合約安全——delegatecall (2)

漏洞分析

這次的攻擊目標依然是獲得HackMe 合約中的 owner 權限,我們可以看到兩個合約中除了HackMe 合約中的構造函數可以修改合約的 owner 其他地方並沒有修改 owner 的函數但是卻可以修改位置slot0的值,而HackMe 合約中插槽slot0表示的便是Lib的地址,那麼我們就先修改Lib的地址為我們的地址,再次調用HackMe 合約時就會運行我們合約中的邏輯,那麼想改哪個位置插槽的值不就都由我們控制了嗎?

攻擊合約

下面是我們本次的攻擊合約:

智能合約安全——delegatecall (2)

接下來我們來看看攻擊的整個邏輯:

1. Attack.attack() 函數先將自己的地址轉換為uint256 類型(這一步是為了兼容目標合約中的數據類型)第一次調用HackMe.doSomething() 函數;

2. HackMe.doSomething() 函數使用delegatecall 函數帶著傳入的Attack 合約的地址調用了Lib.doSomething() 函數;

3. 可以看到Lib.doSomething() 函數將合約中存儲位置為slot0 的參數改為傳入的值,這樣當HackMe 合約使用delegatecall 調用Lib.doSomething() 函數時也將改變自己在slot0 位置存儲的變量的值,也就是將lib 參數(這裡存儲的是Lib 合約的地址)改為我們傳入的Attack 合約的地址。此時之前在HackMe.lib 參數中存儲的Lib 合約的地址就被修改成我們傳入的Attack 合約的地址了;

4. Attack.attack() 函數再次調用HackMe.doSomething() 函數,由於在上一步我們已經將HackMe.lib 變量修改為Attack 合約的地址了,這時HackMe.doSomething() 函數將不再調用之前的Lib 合約而是用delegatecall 去調用Attack.doSomething() 函數。此時我們再來觀察Attack 合約的寫法,發現其變量的存儲位置故意和HackMe 合約保持一致,並且不難發現Attack.doSomething() 函數的內容也被攻擊者寫為owner = msg.sender,這個操作修改了合約中存儲位置為slot1 的變量。所以HackMe 合約使用delegatecall 調用Attack.doSomething() 函數就會將合約中存儲位置為slot1 的變量owner 修改為msg.sender 也就是攻擊者的地址,至此攻擊者完成了他的攻擊。

修復建議

我們在合約的開發中使用delegatecall要時刻注意其被調用的合約地址要始終在我們設計的邏輯內運行,不能讓其有可能超出我們設計時的適用範圍,一旦出現了超出我們預期設計的情況,那麼合約就有可能被不法之徒利用。

如果想了解更多的智能合約和區塊鏈知識,歡迎到區塊鏈交流社區CHAINPIP社區,一起交流學習~社區地址: https://www.chainpip.com/