本體技術視點 | 智慧合約安全與漏洞分析(三)

  • 2019 年 12 月 5 日
  • 筆記

智慧合約安全問題一直是區塊鏈技術體系中探討得比較多的話題之一。無論是以以太坊 EVM 虛擬機為代表的智慧合約體系,還是以 EOS WASM 虛擬機為代表的智慧合約體系,都或多或少地暴露過不同類型的智慧合約漏洞。這些漏洞不僅使得項目方和用戶損失慘重,而且也讓用戶對區塊鏈的安全性產生了質疑。

——題記

01

引言

在上面兩期中,我們介紹了在本體上開發智慧合約可能會遇到的兩種安全威脅:跨合約調用攻擊和強制交易失敗攻擊,並針對這兩種安全威脅給出了相應的解決方案。為了更好地幫助合約開發者規避這些安全漏洞,我們將持續收集這些可能會產生安全威脅的合約案例。同時,社區開發者如果在本體上進行智慧合約開發時遇到其它的安全威脅,可以和我們一起探討,一起共同打造更加安全的智慧合約體系。

這一期將介紹開發智慧合約過程中可能遇到的另外一種安全威脅:存儲注入攻擊。在詳細介紹這種安全威脅前,我們先介紹下本體的智慧合約持久化存儲介面 :

def Put(context, key, value)

該介面主要包含下列三個參數:

1. context - 當前智慧合約的運行的上下文環境,一般情況下為當前智慧合約 hash;

2. key - 當前需要存儲數據的 key 值;

3. value - 當前需要存儲數據的 value 值。

正常情況下,開發者使用這個介面存儲持久化數據時不會有問題,但是在一些複雜應用下,可能會存在安全威脅而被攻擊的情況。

02

存儲注入攻擊

開發者在編寫智慧合約時,通常需要使用持久化存儲去存放一些需要被多次使用的數據。這時,開發者就需要調用智慧合約底層提供的持久化存儲方法去存儲這些數據。為了更好的說明這個問題,這裡我們列舉一個大家比較熟悉的例子。

當我們需要發布某個符合 OEP-4協議的 token 時,需要實現 OEP-4協議中的所有介面。這裡,為了簡化示例,我們只列舉其中的兩個方法:transfer()和 totalSupply()。

SUPPLY_KEY = 'TotalSupply'  def transfer(from_acct, to_acct,amount):      """      Transfer amount of tokens from from_acct to to_acct      :param from_acct: the account from which the amount of tokens will be transferred      :param to_acct: the account to which the amount of tokens will be transferred      :param amount: the amount of the tokens to be transferred, >= 0      :return: True means success, False or raising exception means failure.      """        if CheckWitness(from_acct) == False or amount < 0:          return False      fromBalance = Get(ctx, from_acct)      if amount > fromBalance:          return False      if amount == fromBalance:          Delete(ctx, from_acct)      else:          Put(ctx,from_acct, fromBalance - amount)      toBalance = Get(ctx, to_acct)      Put(ctx, to_acct, toBalance + amount)      TransferEvent(from_acct, to_acct, amount)  return True    def totalSupply():      """      :return: the total supply of the token      """      return Get(ctx, SUPPLY_KEY)

粗略來看,這兩個方法沒有任何問題,transfer()方法主要實現 from_acct 地址到 to_acct 地址的轉賬功能,totalSupply()主要實現獲取當前資產的總發行量功能。但是,若仔細查看transfer()方法里的實現,可以發現該方法並沒有對 to_acct 地址做任何的檢查。

假如這時惡意用戶提供的 to_acct 地址並不是正常的地址,而是一些其他的字元串,例如:「TotalSupply」等,那麼此時在獲取當前資產發行量時就會不準確。這看起來危害程度是有限的,因為我們只列舉了這兩個方法。但是若這本智慧合約中使用了其它持久化存儲,所有的持久化存儲可能都會產生影響。

03

存儲注入攻擊的防範

對於這種合約攻擊,智慧合約開發者如何去防範?可以看到,主要的問題還是在持久化存儲的 key 值上,我們可以將 Put 中的 key 分為兩類:

靜態 key 值 - key 值直接在合約內部定義的值;

動態 key 值 - key 值需要從外部傳入的值。

對於靜態 key,因為在合約內部定義,所以並不會有變動。而動態的 key 值由於從外部傳入,所以 key 值不可預測。在某些特殊的情況下,若動態的 key 值和某個靜態 key 值相同,就會形成我們上面所說的存儲注入攻擊的情況。針對這種情況,開發者可以為 key 值增加 prefix 欄位來防範這種攻擊。

SUPPLY_KEY = 'TotalSupply'  BALANCE_PREFIX = bytearray(b'x01')  def transfer(from_acct, to_acct, amount):      """      Transfer amount of tokens from from_acct to to_acct      :param from_acct: the account from which the amount of tokens will be transferred      :param to_acct: the account to which the amount of tokens will be transferred      :param amount: the amount of the tokens to be transferred, >= 0      :return: True means success, False or raising exception means failure.      """        if CheckWitness(from_acct) == False or amount < 0:          return False      fromKey = concat(BALANCE_PREFIX, from_acct)      fromBalance = Get(ctx, fromKey)      if amount > fromBalance:          return False      if amount == fromBalance:          Delete(ctx, fromKey)      else:          Put(ctx, fromKey, fromBalance - amount)      toKey = concat(BALANCE_PREFIX, to_acct)      toBalance = Get(ctx, toKey)      Put(ctx, toKey, toBalance + amount)      TransferEvent(from_acct, to_acct, amount)  return True    def totalSupply():      """      :return: the total supply of the token      """      return Get(ctx, SUPPLY_KEY)

可以看到,我們為動態傳入的 to_acct 地址在持久化存儲時增加了 prefix 欄位。這樣在存儲時,就不會出現和其它持久化存儲混亂的情況。當然,更好的做法可以給所有的持久化存儲都加上 prefix 欄位。這個例子中,由於只有兩個持久化存儲,我們只需要保證它們互相不受影響即可。

04

後記

以上我們講解了第三種在本體上開發智慧合約時可能遇到的安全威脅,並給出了相關解決方案。另外,本體智慧合約開發者可以使用本體智慧合約集成開發環境 SmartX 中深度集成的高度自動化智慧合約形式化驗證平台 VaaS-ONT 來「一鍵式」精確定位到有風險的程式碼位置,迅速找出原因,有效驗證智慧合約或區塊鏈應用的常規安全漏洞、安全屬性和功能正確性,從而顯著提高安全等級。

以下哪個選項是持有化存儲 Put 介面中 context 代表的含義?

A. 調用的合約程式碼

B. 調用的合約 hash

C. 當前合約 hash

D. 調用入口合約 hash

Tags: