DeFi 的崛起造就ETH需求大增及引發更多的安全風險

近期隨著 defi 火熱崛起,以太坊迎來需求高光時刻,也促使以太坊智慧合約安全漏洞頻發。究竟是什麼原因造成的,這些早已存在的安全漏洞一直沒法得到更好的解決方案,以後要如何更好地防範這些漏洞需要更多的探討。

相比於比特幣而言,以太坊更易發生安全事故。這主要是因為以太坊虛擬機器是圖靈完備的,以太坊可實現函式間相互呼叫、巢狀呼叫,智慧合約間相互呼叫等各種複雜邏輯。而比特幣只實現了基於棧的非圖靈完備的虛擬機器,並只能透過操作碼進行入棧和出棧操作。另外比特幣也沒有複雜的 dapp 應用,所以邏輯上簡單,故而沒有太多空間引發安全漏洞。

 

以太坊上各種 dapp 複雜的智慧合約邏輯是引發安全漏洞的主因。以太坊智慧合約的安全漏洞主要可以分為邏輯問題和合約程式碼問題兩種。

 

邏輯問題

最近頻繁的「閃電貸」攻擊是一個典型的邏輯問題引起的安全漏洞。在各種閃電貸攻擊中你可以看到清晰的邏輯問題。攻擊者只要製造出兩個系統之間的價格差,便能透過閃電貸攻擊獲利。

 

合約程式碼問題

我們知道,幾乎稍微複雜一點的程式碼都或多或少地存在問題(bug)。瞭解出現問題的原因,並且歸納問題類別可以幫助我們更好地防範它們。 ownbit 錢包團隊整理的關於以太坊智慧合約安全最容易出現問題的點。

 

重入(reentrancy)

這是排名第一的問題。所謂「重入」就是一個方法被多次迴圈呼叫。而這通常是合約開發者所意想不到的。例如一個取款合約:

這是一段很簡單的取款合約,讓使用者取走他的 eth 餘額。開發者並沒有意識到這段程式碼可能會被重入。方法是:只要呼叫者是一個合約賬戶,那麼 msg.sender.call 將預設呼叫該合約賬戶的 fallback 函式。攻擊者只需要在其 fallback 函式再次呼叫 withdrawether 就可以源源不斷地取走 eth。

 

發生在 2016 年 6 月,著名的 the dao 攻擊,從而導致了 etc 分叉的事件,就是透過同樣的方法實施攻擊的。從事後看來,這只是一個小小的程式問題(卻造成了如此嚴重的後果)。要修復這個問題也非常容易,只需要將兩行程式碼調換順序即可:

讓你的交易不打包

以太坊區塊的打包機制是按照給予的礦工費(gasprice)進行優先打包,並且每個區塊有總 gaslimit 的限制(目前為每區塊 1200 萬 gas)。所以攻擊者可以製造出若干使用 gaslimit 非常大,並且 gasprice 給得非常高的交易,讓它們優先佔滿區塊,從而讓目標交易無法被打包。

 

所以,在編寫合約邏輯時,不能假設你的交易會在有限時間內被打包,否則就容易受到此類攻擊。著名的「fomo3d」事件就是用了這樣的攻擊方法。

 

fomo3d 遊戲規則是獎勵最後一個購得某個商品的人。每次商品被買入將重置該商品的定時器,如果在定時器達到 0 之前沒有其他購買者,則你將獲得系統的獎勵。攻擊者在 fomo3d 中買入商品,然後同時傳送大量佔用區塊的攻擊交易,以至於在接下來的 13 個區塊內,其他購買者的交易無法被打包。這時定時器達到 0,並認為無其他購買者。攻擊者便獲得了獎勵,完成了攻擊。

 

錯誤使用 tx.origin

如果你發現一個合約使用了 tx.origin,那麼可以留心一下此處可能存在的漏洞。在大部分情況下,我們應該使用 msg.sender 來替代 tx.origin,因為使用 tx.origin 容易引發安全漏洞。

 

很多時候,合約開發人員會假定 msg.sender 和 tx.origin 是相等的,但其實不是。例如:使用者 a 呼叫 合約 b,而合約 b 進一步呼叫 合約 c,那麼在合約 b 和 c 中 tx.origin 都將是 a,而 msg.sender 則一個是 a,一個是 b。

 

一般攻擊者會引誘 a 呼叫一個誘導合約 b,而 b 再去呼叫由 a 部署的目標合約 c,因為 合約 c 錯誤地使用了 tx.origin,合約 b 可以透過傳遞過來的 tx.origin 獲得對 合約 c 的控制權,從而完成攻擊。

 

溢位攻擊

智慧合約裡的資料是可能溢位的,例如:uint256,你覺得很大:2^256。它的確很大,但依然可以溢位。例如一個合約允許對一個資料進行加減,攻擊可以透過對這個資料進行精心策劃的呼叫,讓其透過溢位達到允許執行某些邏輯的目的,從而實現攻擊。

 

fallback 可以 revert

fallback 是可以 revert 的,就是說,你如果向對方轉移 ether,對方可以讓你總是不成功。

 

例如你編寫一段合約,並且依賴於你成功向某個地址轉移 ether,那麼攻擊可以部署一個合約,將其 fallback 寫成 revert 來讓你來的呼叫總是失敗:

selfdestruct 可以定義任意受益者,而不會呼叫 fallback

當你以為可以透過 revert 進行阻止所有人向你付款 ether 時,你可能又錯了。攻擊者透過建立一個合約,並且然後銷燬這個合約。銷燬合約以太坊將退還一部分 ether 作為鼓勵,而這個退還可以指定任意受益者,而對方的 fallback 函式不會被呼叫。

 

這就是說,開發者要意識到你沒有辦法完全阻止別人向你的合約賬戶轉移 ether。

 

未正確使用 delegatecall

在使用 delegatecall 時,要注意上下文(即 msg.sender 等)的變化。用 call 進行合約呼叫時,上下文被切換至被呼叫合約。而用 delegatecall 進行合約呼叫時,上下文依然在本合約。

 

delegatecall 和 call 不同的呼叫上下文也是合約安全漏洞較常出現的地方。

 

不同方法傳氣不一樣

當我們進行 ether 轉移時,不同的方法傳氣(gas)不一樣。使用 send() 和 transfer() 傳遞氣僅為 2300,而使用 call.value()() 則將剩餘的氣全部傳遞。因此,最新的安全規範是建議使用 call 而不是 send 或者 transfer 進行 ether 轉移。

 

如果你發現一個合約還是使用 send 或者 transfer,那麼你可以製造出目標合約,讓其轉移 out of gas。

 

以上這些點是合約程式碼最常出現問題的點。每個錯誤的原因都比較原子化,理解相應的原理可以幫助我們有效地避免這些問題。當合約邏輯複雜時,一定會有更加複雜、隱藏得更深的邏輯問題,這時,這些原子點的檢查依然可以幫助我們找到它們。

 

以太坊智慧合約的安全問題主要是因為其「過於靈活」引起的。靈活性和安全性如同天平的兩端。以太坊選擇了靈活性,某種程度上便把安全性的潛在風險留給了市場。

 

一個 defi 專案能否安全穩定地執行,或是會被駭客攻擊,取決於合約開發人員對原理的理解、對細節的把控,以及嚴肅認真的態度。線上合約犯錯的代價是巨大的,這就對合約開發人員提出了更高的要求!


免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读