以太坊智能合約安全

引言

智能合約就是自主執行的合約,其條款是用代碼規定的。html

雖然這個概念已經存在一段時間了,但至少從1996年Nick Szabo提出了這一律念以來,直到圖靈完備的以太坊區塊鏈來臨,智能合約的使用才變得廣泛。git

以太坊的智能合約存在於合約地址裏,能以交易命令來調用。用代碼編寫並存放在不可更改的公鏈上的合約執行起來會產生必定的風險與安全問題。咱們將會在本文中討論這些問題和可能的緩解措施。github

代碼即法律?

對智能合約理念的字面解釋形成了「代碼即法律」 ( 「Code is Law」 ) 的範式理解,意思是那些智能合約具備約束力,並被詮釋爲合法文件。全部意識到沒法建立徹底無錯代碼的軟件工程師在想到計算機程序具備合法約束力的時候,手心都會捏一把汗。這裏存在不少明顯的問題:算法

  1. 代碼有漏洞。寫無bug代碼是一件很是困難的事情,即便作好了全部可能的預防措施,在至關複雜的軟件中也總會存在乎想不到的執行路徑或者可能的漏洞。編程

  2. 法律合約也須要解釋與仲裁。建立毫無漏洞法律合約是很是困難的。在任何大型合約中,拼寫錯誤可能出現,一些條款須要解釋和仲裁。這就是爲何在出現爭議時咱們須要法庭。若是在某個法律合約中,40頁中有39頁標定的銷售價格爲100美金,而在某一頁上的價格中多了個「0」,法院將以「合約精神」爲準進行裁定。而計算機只會執行寫好的條款,區塊鏈的不變性增長了這個問題,由於合約沒法輕易修改。安全

  3. 軟件工程師不是法律專家,反之亦然。起草一份好的合約須要與編程不一樣的技能,一名可以編寫很是完善計算機程序的人員不必定會寫法律合約。bash

兩起著名的利用智能合約漏洞的事件

The DAO 攻擊

這件事不少人都早已談了許多,咱們就不在這重複了。您能夠在這裏找到關於攻擊和藹後的完整概述。併發

簡單來講,在 2016 年 6 月,一名黑客企圖轉移一大筆衆籌資金 (350 萬個 ETH, 佔當時ETH總數的15%)至他本身的子合約,這筆資金被鎖定在該子合約中 28 天,於是你們要與時間競賽尋找解決方案。app

這事件要注意的重點在於,攻擊者是經過使合約以意料以外的方式運行而發起的攻擊。這個事件中,攻擊者利用了 可重入漏洞( Reentrancy Vulnerabilities )。咱們將會在這篇文章中對可重入進行深刻討論。ide

黑客攻擊Parity事件

事實上,這是第二次 Parity 所提供的的多重簽名錢包合約受到黑客入侵了。衆多創業公司使用的多重簽名錢包的邏輯大多經過庫合約實現。每一個錢包都包含一個輕量級的客戶端合約,鏈接到這個單點故障(譯註:即上述庫合約)。

-Parity 多重簽名錢包結構-

這個庫合約中存在一個重大的漏洞,問題在於其中一個初始化函數只能被調用一次。

2017年11月,一名男子經過實施合約初始化,將本身變成了合約全部者。這容許他調用全部者才能調用的 函數,他利用這一特權調用瞭如下的函數:

// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
    suicide(_to);
}
複製代碼

這至關於一個能使合約無效的自毀按鈕。調用這個函數會致使客戶合約的全部資金有可能被永久凍結。

直到撰寫本文之時,該事件究竟是黑客形成的故意攻擊仍是意外仍然是個謎,儘管肇事者聲稱這是意外行爲。

兩次攻擊都說明了即便是由以太坊生態系統的大佬來寫相對簡單的合約,也容易出現基本的錯誤,帶來嚴重的後果。

已知的漏洞與陷阱

私鑰外泄

使用不安全的私鑰純粹是用戶的失誤,而不是一個漏洞。然而,儘管如此,咱們仍是要指出來,由於私鑰外泄老是意外地發生,有些玩家專門從不安全的地址中竊取資金。

把開發地址(如 Ganache/TestPRC 使用的地址)用於生產的事情常常發生。這些地址是由公開的私鑰生成。一些用戶甚至無心識地把這些私鑰導入到錢包軟件,由於他們使用 Ganache 的種子詞(seed words)生成了相同的私鑰。攻擊者則監視着 TestPRC 地址,無論多少數量的以太幣只要轉移到以太坊主鏈上的 TestPRC 地址都會馬上消失(在2個區塊內)。一項有趣的研究對這一十分有利可圖的「清掃(sweeping)」活動進行了調查,並發現每一個清掃者(sweeper)的帳戶都設法累積了高達2300萬美圓的資金。

可重入與競態條件(Race Conditions)

若是一個函數在執行完成前被調用了數次,發生意料不到的行爲時,可重入漏洞就可能出現。

讓咱們看看下面這個函數,它能夠用於從合約中提取調用者的總餘額:

mapping (address => uint) private balances;
function payOut () {
    require(msg.sender.call.value(balances[msg.sender])()); 
    balances[msg.sender] = 0;
}
複製代碼

調用 call.value() 會致使合約外部代碼的執行。若調用者是另外一份合約,這就意味着合約回退措施的執行。這可能會在餘額調整爲 0 以前再次調用 payOut(), 從而得到比可用資金更多的資金。

這種狀況下的解決方法就是使用替代函數 send() 或 transfer() ,後二者函數能爲基礎運做提供足夠的 Gas,而想要再次調用 p*ayOut()* 時 Gas 就不足了。

若合約含有兩個共享狀態的函數,那麼不須要重複調用函數也可能會發生類似的競態條件(Race Conditions)。所以,最好的作法是在轉帳前更改狀態,即轉移資金前,在上述代碼中把餘額設爲 0。

The DAO 攻擊利用了該漏洞的一種變體。

下/上溢

餘額通常用無符號的整數表示,在 Solidity 語言中一般爲 256 位數字。當無符號整數上溢(overflow)或下溢(underflow)時,其數值會發生明顯變化。讓咱們看看下面一個比較常見的下溢例子 (爲了清晰一點我把數字縮短了):

0x0003
- 0x0004
--------
  0xFFFF
複製代碼

這裏很容易看出問題,減去一個比可用餘額大的值便致使下溢。獲得的餘額是一個很大的數字。

還要注意的是因爲舍入偏差(Rounding Errors),在整數中算術分割(Arithmetics Division)是很麻煩的。

解決方法是時刻對代碼進行下溢、上溢檢查。使用安全數字庫能協助檢查,好比 OpenZeppelin 的 SafeMath

交易順序假設

交易進入未確認的交易池,並可能被礦工無序地包含在區塊中,這取決於礦工的交易選擇標準,有多是一些旨在從交易費中獲取最大收益的算法,但也能夠是其它任何標準。所以,打包在區塊中的交易順序與交易生成的順序徹底不一樣。所以,合約代碼沒法對交易順序做出任何假設。

由於交易在記憶池(Mempool)是可見的,其執行是可預測的,因此除了合約執行出現意外結果的狀況,還有一個可能的攻擊面。交易打包中可能出現的一個問題就是,延遲某個交易可能被流氓礦工用做我的利益。事實上,可以在交易執行前意識到某些交易(的存在)對任何人來講都是有利的,而不只僅是礦工。

對時間戳的依賴

時間戳(Timestamps)是由礦工生成。所以,合約不該該讓關鍵操做依賴於區塊時間戳,例如把時間戳用做一個生成隨機數的種子。Consensys 在他們的指導手冊中給出了「12分鐘規定」,代表若是你依賴時間戳的代碼可以處理 12 分鐘的偏差,那麼使用block.timestamp 是安全的。

短地址攻擊

Golem team 揭露了一個有趣的攻擊,詳情請看這裏。該漏洞影響了 ERC20 代幣傳輸和一些相似的合約,該漏洞的問題在於交易字節代碼能夠是任意大小,而以太坊虛擬機(Ethereum virtual machine,簡稱EVM)會在其尾部缺失的字節填充0。

實施該攻擊須要找到一個以十六進制(hex)形式表示且結尾爲若干個 0 的地址,並在提幣請求中省略這些結尾的 0。當該合約發起一個轉帳請求時,短地址被插入,其他的交易字節代碼被移位。

舉個例子,省略結尾的兩個 0 會致使交易數據中地址以後的字節發生 1 個字節的移位。地址後面是交易數據中的參數,一般是無符號的前置 0 的 256 位整數。這些前置的 0 會移入地址字段,使地址有效並確保交易目的地是正確的。

參數字段中一個字節的移位也很容易致使提幣量變爲原來的256倍。在EVM用 0 填充缺失的結尾字節後,交易成功,而後轉走 256 倍的金額。

所以,利用省略兩個十六進制0的地址的漏洞使攻擊者能夠從一個餘額爲 1000 個代幣的帳戶中提取 256000 個代幣,以此類推。省略 4 個結尾的 0 則是 2^16 倍。

爲了不這種攻擊,你的合約應該驗證地址。

拒絕服務攻擊(DoS Attacks)

有時經過使合約交易超過可以包含在一個區塊中的最大 Gas 量,來迫使合約交易失敗。在這篇拍賣合約的解讀中解釋了這個經典例子。迫使合約退還大量沒有接受的小投標會增長 Gas 消耗量,若是能耗超過了區塊 Gas 上限,那麼整個交易失敗。

這個問題的解決方法是避免許多交易調用可能由相同的函數調用引發的狀況,尤爲是若是調用次數會受到外部影響。

推薦的付款模式是讓客戶請求轉帳,而不是一次性轉帳出去,如 Solidity 官方文件所述。

緩解措施與結論

爲了強調「代碼即法律」這一範式理解的危害,本文咱們闡述了可能發生的漏洞以及過去攻擊者是如何利用這些漏洞的例子。

最近的歷史事件代表在公鏈上執行圖靈完備的智能合約是危險的,其安全性遠不足以取代傳統法律系統的語言準確度與解釋和仲裁空間。

但這並不意味着咱們應該拋棄智能合約。智能合約是很是有用的工具,能開發出有趣的應用程序。然而,咱們不能認爲智能合約能取代具備法律約束力的合約,它只是用於自動化的補充工具。

另外,咱們應該作好預防措施去避免漏洞:

  • 使用開放的資源與社區接受的庫合約的實質標準 (de facto standards),例如 Open Zeppelin’s contracts

  • 使用推薦的模式與最優操做指導手冊,例如 Consensys 提供的。

  • 考慮由信譽好的供應商審覈您的智能合約。

原文連接: medium.com/cryptronics…

做者: Stefan Beyer

翻譯&校對: 楊哲 & Elisa

稿源:以太坊愛好者(ethfans.org/posts/ether…

相關文章
相關標籤/搜索