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

clipboard.png

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

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

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

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

第一種函數

clipboard.png

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

第二種區塊鏈

clipboard.png

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

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

第三種設計

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

相關文章
相關標籤/搜索