智慧合約(in)安全性–錯誤的演算法(Bad Arithmetic)

買賣虛擬貨幣
這篇部落格文章是該系列文章的第二篇,將講述一些簡單的現實中智慧合約安全性Bug,駭客們是如何利用它們造成系統的影響以及提供相應的修復程式碼。到目前為止,我們已經實現了3,000萬美元的修復挽救,即直接歸因於智慧合約安全漏洞的2.5億美元的損失。這次我們將分別存入兩筆存款,分別為:57,896,044,618,658,097,711,785,492,504,343,953,926,634,992,332,820,282,019,728,792,003,956,564,819,968個代幣,總計0個代幣!這篇部落格文章將專門介紹算術運算的Bug(例如整數溢位)儘管智慧合約提供了具有獨特新穎性的執行環境,但這無疑不是一個新問題。演算法的Bug幾乎佔據了SEI CERT Oracle Java編碼標準中大部分的吸引力。其他開發語言也遭受完全相同的可怕情況,漏洞對映到幾個常見的Bug中。智慧合約主要面臨與公共執行環境的安全,不可變更的性質,不同的利益相關者類別以及邏輯複雜性相關的獨特挑戰。讓我們看一下BeautyChainToken的合約程式碼,該程式碼可以在Etherscan上可以輕鬆找到。透過瀏覽程式碼可以發現在最頂部附近有一個名為SafeMath的庫的實現,隨後是一系列相互依存的合約。忽略周圍其他的程式碼干擾,但請注意經常引用變數to,from,amount和balances以及稱為transfer()和approve()的函式,這是一個不到300行的全功能程式碼。讓我們從第255行開始檢查batchTransfer()函式。它有兩個引數-第一個引數(_receivers)是目標帳戶地址的列表,第二個引數(_value)是要轉移到每個目標帳戶地址的代幣數。第256行計算列表中接收方地址的數量,然後第257行可以計算從傳送方帳戶餘額中提取的總金額。第259行確保傳送方有足夠的餘額,然後第261將第257行向下調整之前的總金額。最後第262行開始迴圈,透過將每個接收者的餘額向上調整_value來執行單獨的轉帳。255 function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {256   uint cnt = _receivers.length;
257   uint256 amount = uint256(cnt) * _value;258   require(cnt > 0 && cnt <= 20);259   require(_value > 0 && balances[msg.sender] >= amount);260261   balances[msg.sender] = balances[msg.sender].sub(amount);262   for (uint i = 0; i < cnt; i++) {
263     balances[_receivers[i]] = balances[_receivers[i]].add(_value);264     Transfer(msg.sender, _receivers[i], _value);265   }266   return true;267 }Solidity中最常見的資料型別之一是unsign256位整數(uint256或uint),它可以表示0到2^256-1之間的任何值。如果合約程式碼執行一個普通的算術運算,得到的結果超出了這個範圍,則結果會悄悄地被包裝起來,合約也將會繼續使用錯誤的資料,這是非常可怕的結果。出於這個原因,上面顯示的合約特別是使用了前面看到的safemath庫中的專用的.sub()和.add()函式(分別位於第261行和第263行)。如果遇到下溢或上溢,將編寫這些通用函式來中止操作,這將阻止執行繼續處理不良資料。您可能會問為什麼第257行使用普通的'*'而不是SafeMath中的.mul()。
Bug!關於第257行,如果_value設定為2^255,然後由於在前一行計算出的cnt為2而加倍,則由於迴繞(wrap-around)效應,結果將被儲存進amount為0(而不是2^256)。這是我們旨在解決的確切錯誤-現在將繼續處理錯誤資料,繼續執行。要求的_value很大,但總計算amount為0!amount為0金額時餘額檢查將會被忽略第259行,並繼續透過第261行上的進行餘額調。然而在執行單個傳輸的迴圈利用了巨大的_value變數(不再引用數量變數)。這意味著迴圈將使兩個目標餘額分別增加2^255。累積增加!發生了什麼?攻擊者呼叫了上面所示的batchTransfer()函式,併為其提供了一個_receivers列表,該列表包含兩個地址和一個_value引數設定為2^255。在Etherscan事務中可以清楚地看到這一點(單擊“解碼輸入資料”)。如前所述,地址的cnt計算為2,因此乘以2^255的值將導致溢位到0。此錯誤資料將繼續執行。透過各種引數檢查,發件人的餘額減少到0,最後將_value的兩筆存款存入_receivers餘額。順便說一句,如果您還沒有猜到的話,本帖子第一段中提到的大量數字恰好是2^255。大約0.02美元的真金白銀來資助攻擊交易,兩筆大量的代幣是憑空建立的,並存放到_receiver地址中。這導致了對該代幣合約的信任喪失,從而徹底破壞了其價值。讓我們修復它。我們要做的就是用.mul()替換第257行上的普通“ *”,如下所示。字首為“ –”的行將從合約程式碼中刪除,而字首為“ +”的行將被新增。-    uint256 amount = uint256(cnt) * _value;+    uint256 amount = uint256(cnt).mul(_value);回想起來很簡單嗎?回看一下過去Bug,幾乎所有安全Bug都變得非常明顯,但二十多年來,開發人員一直在犯這些相同的錯誤。請注意,上述修復程式碼對於BeautyChainToken來說是沒有意義的,因為智慧合約的軟體沒有更新。一旦部署了智慧合約,它們除原始程式碼之外就基本上不可更改且不可阻擋。因此,我們非常簡單的解決方案(僅由幾個字元組成)只能成為未來的一個教訓,強調了“不要讓算術結果出錯,然後繼續使用錯誤資料執行”。
沒那麼簡單。代幣是智慧合約的一個用例之一,都是由頂級專家進行非常謹慎的開發。SafeMath庫的存在表明這是一個已知的問題區域,但是我們上面看到的程式碼確實使用了SafeMath庫。實際上,ERC-20代幣標準本身就有幾個已知的問題,這些問題僅能部分解決(最好)。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;