以太坊合約的安全性弱點,你都繞開了嗎?

clipboard.png

很多以太坊的智能合約控制着有實際價值的數字資產。因此,保證合約沒有安全漏洞是十分重要的事情。這幾期爲大家帶來一篇 2017 年對以太坊合約攻擊調研的文獻,來幫助大家避免以太坊智能合約設計中的一些可能導致安全性問題的弱點。在這裏,你也可以看到,導致以太坊分叉的著名事件 The DAO 攻擊,其原理是什麼。

這篇文獻分兩部分,第一部分介紹了一些如果對 Solidity 語言和智能合約不當使用會導致問題的弱點;第二部分則用一些實例展示了這些弱點可能會導致怎樣的問題。

我們今天還推送了另一篇文章作爲背景資料,爲不熟悉智能合約和 Solidity 語言的讀者介紹一些背景內容。

不當使用會導致問題的點
合約內函數調用
在使用 Solidity 編寫智能合約時,可以調用其他合約中的函數。假設 Alice 合約裏有一個 ping(uint) 函數, c 是一個 Alice 合約在以太坊上的地址。如果其他合約(或 Alice 合約自身)想要以參數 42 調用 ping 函數,有三種方式:

第一種

clipboard.png

call 調用:通過合約地址,合約函數,函數簽名和調用參數進行調用。如果被調用函數中有修改合約變量的代碼,將修改被調用合約中相應的變量。

第二種

clipboard.png

delegatecall 與 call 類似,區別是 delegatecall 執行時,僅僅使用被調用函數的代碼,而代碼中如果涉及到合約變量的修改,則是修改調用者合約中的變量。如果被調用的函數中有 d.send(amount)的指令,表示向地址 d 轉一定數額的以太幣,在 call 模式下這筆錢從被調用合約的餘額中轉出。在 delegatecall 模式下將從調用者合約的餘額中轉出。

因此, delegatecall 是更危險的命令,如果這一命令加載的函數代碼是合約編寫者不可控的,可能會導致合約的錢被轉走或合約被銷燬等嚴重後果。

第三種

clipboard.png

這第三種調用方式在論文中被稱爲直接調用 (direct call). 它先在合約裏聲明瞭 Alice 合約需要調用的函數,然後調用它。這種方式與以上兩種方式在異常處理上會有區別。

需要注意的是,以上三種方式如果將函數名或者參數類型設置錯誤,則會調用回退函數 (fallback function). 如果是因爲筆誤打錯了內容,可能會觸發本不該執行的回退函數中的代碼。

Gasless Send
在 Solidity 中,如果變量 rec 的類型爲 address, 那麼 rec.send(amount) 表示由合約向地址 rec 轉賬數額爲 amount 的 wei. (10^18 wei = 1 ether ) 在這個執行的過程中,還會觸發地址 rec 的回退函數。如果回退函數執行過程中消耗的 gas 大於 2300,則會觸發一個異常,導致轉賬失敗。

異常處理
在背景介紹中我們提到過,使用 Solidity 執行智能合約時會拋出異常,但是不同的合約內函數調用方式對異常(exception)的處理方式不一樣。

如果合約執行過程中沒有函數調用,或者只有 direct call 直接調用,那麼當觸發一個異常的時候,視爲合約執行失敗,直接停止合約的執行,回滾執行過程中的轉賬和對合約變量的修改等操作,並扣除全部的交易費用。

如果通過 call, delegatecall 或 send 調用其他合約函數,在執行期間觸發的異常不會影響原有函數。也就是說,如果在執行 send 的觸發的回退函數過程中,如果 gas 不足引起了異常,轉賬會失敗,但是原有合約會被成功地執行。

如果對這一點缺乏足夠的理解,錯誤地認爲合約執行成功意味着 call 調用也一定成功,錯誤地認爲沒有觸發異常就意味着 ether 轉賬成功,就可能導致合約有安全性問題。正確的做法應當是通過函數調用返回的結果判斷其執行是否成功。而一些研究表明有 28% 的合約沒有檢查返回結果。(當然,這不意味着一定有安全問題)

重入問題
Solidity 中回調函數的機制,可能會讓合約調用其他函數後,被調用的函數又調用了調用者合約的函數,造成循環,下面是一個例子

假設區塊鏈上已經如下的合約 Bob,如果 sent 變量爲 false, 就向給定地址發送一筆錢。

clipboard.png

而 Mallory 是攻擊者惡意構造的合約,代碼如下所示。

clipboard.png

Bob 合約設計的本意是,如果 sent 變量爲 false, 就向給定地址發送一筆錢。然而,當這筆錢發往攻擊者合約時,會觸發攻擊者合約的回退函數,回退函數再次調用 ping 函數,如此無限循環,直到交易費耗盡或調用深度達到上限 1024 次觸發異常。但之前提到了,對於 call 調用的函數在執行過程中觸發的異常,不會影響原來的函數的成功執行。也就是說,除了最後一步轉賬會失敗,之前的轉賬都會成功。

幾種攻擊
接下來,我們介紹幾種利用上面提到弱點的攻擊例子。

DAO 攻擊
DAO 攻擊是以太坊歷史上最著名的攻擊,盜走了價值 6000 萬美元的以太幣。以太坊社區通過強行回滾硬分叉了以太坊,導致了以太坊和以太經典兩條分叉鏈並存的局面。

下面是一個簡化版的 DAO 智能合約,但足以描述 DAO 合約的漏洞。

clipboard.png

這個合約的功能很簡單,任何人可以向指定地址捐獻以太幣,受捐贈人可以提走自己受捐贈的幣。

而攻擊者通過以下的合約,就可以大量轉走合約中的幣。

clipboard.png

其原理與上文所說的重入問題完全一樣, SimpleDAO 合約的 withdraw 函數執行時向攻擊者合約轉賬,轉賬會觸發攻擊者合約的回退函數,攻擊者合約的回退函數會重新調用 SimpleDAO 合約的 withdraw 函數,形成一個循環。當循環因爲各種原因結束的時候,除了最後一步,之前的執行都不會失敗。攻擊者轉出了大量的錢。

另外,這個合約沒有考慮整數溢出問題,因此有如下攻擊成本更低的方案

clipboard.png

在這個合約中,攻擊者設計了一個函數 attack, 當這個函數被執行的時候,攻擊合約先給自己捐贈 1 wei, 然後把這 1 wei 取出來。在取錢的時候,會觸發攻擊者合約的回退函數。與之前的攻擊不同,這次我們只利用重入問題 1 次,也就是 withdraw 函數被執行了兩遍。在 withdraw 第二次向攻擊者轉賬以後,攻擊者不再調用 withdraw.

於是 withdraw 函數中的轉賬操作 msg.sender.call.value(amount)() 發生了2次,自然地,它的下一行也會被調用 2 次。這兩次被調用將 credit[攻擊者地址] 變成了 -1 wei, 會被虛擬機解讀爲 2^256-1 wei. 這時,攻擊者可以從中取出幾乎無限多的錢出來。

特別的是,即使 withdraw 函數在轉賬後檢查 send 執行是否成功,也只能防範第一種攻擊。

以太王座
考慮下面一個遊戲合約,在遊戲中,大家將競爭一個王座。後來者可以通過向王座上的人支付一筆錢來取而代之,每一輪取得王座需要的錢都要比上一輪高。最後取得王座的人有額外的收益。(沒有在合約中體現。)

clipboard.png

這個合約看上去沒什麼問題。事實上,他人在向王座上的人(地址)支付費用的時候,會觸發那個地址(如果是一個合約)的回退函數。如果王座上合約地址的回退函數需要的交易費過高,會觸發 gasless send 的問題,就會導致轉賬失敗。但後續變更王座擁有者的代碼還會照常執行,新來者可以毫無成本地獲得王座。

修改這一問題的思路看上去很簡單,只要將轉賬的代碼 king.send(compensation) 變成 if(!king.call.value(compensation)())throw; 來判斷一下轉賬是否成功就可以了。然而這會導致另一個問題。王座上的地址(合約)將自己的回退函數設定成一定會觸發異常,例如 function(){throw;},就沒有人有能力將他從王座上趕下去了,因爲所有轉賬的結果都會失敗。

以上就是這一期的內容,在接下來的文章中,我們將會介紹文獻中提到的其他的 Solidity 的弱點與可能導致的問題。

參考文獻:
[1] Atzei, Nicola, Massimo Bartoletti, and Tiziana Cimoli. "A survey of attacks on ethereum smart contracts (sok)." Principles of Security and Trust. Springer, Berlin, Heidelberg, 2017. 164-186.


Conflux 是致力於打造下一代高性能的 DAPP 公鏈平臺

歡迎關注我們的微信公衆號:Conflux中文社區(Conflux-Chain)

添加微信羣管理員 Confluxgroup 回覆“加羣”加入 Conflux官方交流羣

clipboard.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章