合約實現中的事與願違 —— 實現與設計不符
針對區塊鏈安全問題,成都鏈安科技團隊每一週都將出智慧合約安全漏洞解析連載,希望能幫助程式設計師寫出更加安全牢固的合約,防患於未然。
引子:出塵花品愛池荷,零落秋風可奈何 —— 《病中詠秋荷》清·蔡桓
本期話題
邏輯判斷失誤事與願違,功能實現不周弄巧成拙
智慧合約作為區塊鏈技術革命的核心,乘著以太坊興起的潮流,發展至今已經兩年有餘。智慧合約的理念最早可以追溯到1995年,幾乎與網際網路同時出現。廣受讚譽的密碼學家尼克薩博最早提出了這個術語,他也是為比特幣打下基礎的學者之一。隨著比特幣的出現和應用,讓智慧合約的理念有了實現的機會。
從原理上來說,智慧合約的執行依靠的是條件的觸發,也就是類似於程式中的if-then語句。也就是說,智慧合約對於其中合同條款的執行取決於預先編譯好的邏輯判斷,進而與真實世界的資產進行互動。本期,我們歸納總結了一些在從設計到實現的過程中出現安全漏洞。
智慧合約愈發複雜
我們都知道,一個智慧合約包含兩部分:程式碼邏輯和資料。
相比於比特幣的指令碼語言,智慧合約發展的一個終極目標就是“圖靈完備”,簡單來說,就是無論怎樣複雜的交易邏輯或功能都可以實現。
智慧合約的開發經過一段時間的沉澱,已經由最初簡單的轉賬、付款等功能需求,向越來越多的交易功能和邏輯進化,比如凍結、暫停、中止、授權。根據複雜性理論,越複雜的系統越容易有出錯的可能。因此,越是功能強大的智慧合約,其邏輯就越是複雜,也越容易出現邏輯上的漏洞。
開發合約也會言多必失?
打個比方來說,邏輯更加複雜的程式碼相當於更龐大的城池,在容納更多人口的同時,需要防守的地方也更多。有些精心設計而又容易忽視防範的地方,比如錯綜複雜的下水道系統,正是容易讓整個城池淪陷的重大隱患。
同樣,在智慧合約中,開發者為自己的合約設計的特殊功能意在穩固代幣的市值或者專案的壽命,而正是在這些邏輯和功能中,一個細微的失誤,比如>、<、+、-這些符號的錯誤就可能導致整個邏輯與預想出現嚴重的偏差,留下致命的隱患。
程式碼實現失誤漏洞總結
我們將程式碼實現過程中產生的失誤分為兩種:
1. 邏輯判斷錯誤
Solidity的if或者require等條件判斷表示式對合約程式碼執行做出限制,開發者如果在編寫合約時寫出了錯誤的判斷條件,將會造成比較大的邏輯錯誤,影響合約正常使用。
a) transferFlaw
在transferFrom()函式中,當對allowance值做校驗的時,誤將校驗邏輯寫反,從而使得合約程式碼的邏輯判斷錯誤。有可能造成溢位或者任何人都能轉出任何賬戶的餘額。
我們來看一個案例
if( allowed[from][msg.sender] >= value ) return false;此處校驗邏輯寫反了,導致只要授權值allowed[from][msg.sender]小於轉出額度,那麼都能轉賬成功,利用這個漏洞,可以不經授權就可以轉出他人賬戶中的代幣。
l 漏洞修復
推薦使用require(allowed[from][msg.sender] >= value);語句對授權額度進行判斷
b) pauseTransfer-anyone
以如下案例合約為例
onlyFromWallet 中的判斷條件將 == 寫反了,寫成了!=,使得除了 walletAddress 以外,所有賬戶都可以呼叫 enableTokenTransfer() 和 disableTokenTransfer() 函式,這兩個函式可以開啟或者關閉合約的轉賬、授權以及燒幣功能,進而影響合約的正常使用。
l 漏洞修復
使用正確的require判斷語句:
c) allowAnyone
分析如下案例合約
在transferFrom函式中,轉賬前未對allowed進行校驗,轉賬後對allow的計算也未使用SafeMath, 使得任何賬戶都可可以不經授權就能夠轉出他人賬戶中的代幣,並且,如果轉賬額度_value大於allowed[_from][msg.sender],allowed[_from][msg.sender] -= _value將發生溢位。
l 漏洞修復
i. 在轉賬前增加對allowed的檢查,require(allowed[_from][msg.sender]>=_value);;
ii. 使用SafeMath對allow進行運算,
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
d) approve-with-balance-verify
部分合約在函式approve()中,增加對被授權賬戶餘額的校驗,要求授權的_amount小於或等於當前餘額。
一方面,對餘額的校驗並不能保證被授權賬戶一定可以轉出這個數量的金額:
i. 在approve之後,token的所有者自己通過transfer函式,把token轉走,導致餘額小於allowance。
ii. approve給多個人,其中一個人進行transferFrom操作後,可能導致餘額小於之前給其他人approve過的值。
另一方面這個校驗可能導致外部合約(如以0x協議為基礎的去中心化交易所)無法正常呼叫,必須由Token 專案方提前轉入一筆數額巨大的 Token 至中間賬戶才能繼續執行。
例如下面這個合約
l 漏洞修復
去掉balances[msg.sender]的校驗:
2. 功能實現與設計不符
我們來詳細分析如下案例合約:
上述兩個函式重寫了ERC20標準中的transfer和transferFrom函式,添加了對凍結賬戶的檢查。
在transfer中,如果msg.sender被凍結,那麼其不能進行代幣交易。
但是,我們注意到,在transferFrom中,並沒有對代幣轉出地址(_from)的檢查。
如此一來,一個被凍結的地址可以通過授權第三方,然後通過第三方地址向目標地址傳送代幣,違背了設計中對凍結地址的限制。
l 漏洞修復
在合約正式上鍊前,專案方應對合約進行充分的測試,確保其實現完全符合設計。
我本將心向明月,奈何明月照溝渠
以上的安全漏洞都是開發人員在實現某些功能需求時,出現低階錯誤或者只追求完成功能需求導致的。相信在漏洞被曝光或者被攻擊者利用時,開發者的內心也是崩潰的,感覺枉費了一番苦功夫。在沒有相關測試、審計團隊的協助下,對於此類實現與設計不符的漏洞可能真的束手無策,尤其是關於合約邏輯的檢測,可能需要用到形式化驗證運用數學建模的方法對於合約進行描述之後才能查遺補漏。但是,汲取他人教訓並且用於提高應該是每個先驅者在探索新領域時具備的素質,一個人可能會走的更快,而一群人,會走得更遠。