1. 程式人生 > >智慧合約安全事故回顧(2)-BEC溢位攻擊

智慧合約安全事故回顧(2)-BEC溢位攻擊

講溢位攻擊之前,先給大家講個故事:2014年的時候,美國的賓夕法尼亞州的某個小鎮上發生了一個烏龍事件,徵兵系統對一萬多名1893年到1897出生的男子發去信函,要求他們註冊參軍,否則面臨罰款和監禁。收到信函的人啼笑皆非,因為這些信函指明的人都是大部分都是他們已故的祖父外祖父。導致這個事件的原因就是“千年蟲”。

嚴格的說“千年蟲”屬於程式的一個BUG。因為在上個世紀,計算機的儲存空間很小,使用人員為了最大化利用計算機的儲存空間,規定了在計算機中儲存年份的時候使用兩位數字來表示,如“1998”年,那麼計算機中的儲存值則為“98”。當千禧年來臨的時候,這個值就會變成“00”,這個時候計算機就不知道這個“00”代表的是1900年還是2000年,所以才會出現剛才文章開始的烏龍事件,這也是我們本文要講述的”溢位“的概念。

 

事件介紹

2018年4月23日,一款名為BEC的代幣被黑客攻擊。黑客利用合約內的漏洞,短時間向外部的賬戶轉入了天價的合約代幣, 導致該代幣價格迅速縮水幾乎歸零。攻擊手法被披露的24小時內,就有三十多個合約被類似手法攻擊。

漏洞原因

在solidity語言中,對int型別的資料變數規定了長度,如uint8代表的是無符號的8位整數,即0到255。假如有下面一個簡單的合約:

pragma solidity ^0.4.25;
contract test{
  function add(uint8 _a) public pure returns(uint8){
      return _a+1;
  }
   
}

可以發現傳入的引數是一個uint8型別變數,它的範圍在0-255,如果輸入的值是255,那麼返回的結果會是什麼呢?有興趣的讀者可以到remix中試一下,返回值會是0,如果輸入256,返回結果會是1。造成這樣的原因主要跟資料在計算機中的儲存有關,計算機只給uint8的型別變數分配了長度為8的空間,最大值為255,如果超過這個值會產生進位之後被截斷,導致儲存的8位全部都是0,這就造成了整數溢位。

下面看一下BEC合約中的一個函式:


function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
  uint cnt = _receivers.length;
  uint256 amount = uint256(cnt) * _value;
  require(cnt > 0 && cnt <= 20);
  require(_value > 0 && balances[msg.sender] >= amount);

  balances[msg.sender] = balances[msg.sender].sub(amount);
  for (uint i = 0; i < cnt; i++) {
      balances[_receivers[i]] = balances[_receivers[i]].add(_value);
      Transfer(msg.sender, _receivers[i], _value);
  }
  return true;
}

這個函式的目的是實現一個批量轉賬的功能,receivers是接受者的陣列,value是轉賬金額。重點關注

 uint256 amount = uint256(cnt) * _value;

這裡定義了一個uint256型別的變數amount來接收轉賬的總金額,後續會通過這個金額的值和使用者所傳送的金額比較來判斷使用者是否能夠傳送這麼多的代幣。那麼重點來了,如果uint256(cnt) * _value的值超過uint256,不就產生了溢位了嗎?攻擊者通過傳遞兩個賬戶,__value為2的255次方(實際上是轉換成了16進位制),2*2^255=2^256完成了溢位,amount的值為0。這個邏輯下的amount能夠通過後面的所有校驗,最後傳送給兩個賬戶的值確是2的255次方的代幣。

BEC車禍現場連線:https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f

防範

跟攻擊手段一樣,針對BEC的溢位攻擊的防範也非常簡單。我們可以利用safeMath庫來避免這種情況。把 uint256(cnt) * _value改成 uint256(cnt) .mul(value)即可。相信稍微有點solidity程式設計經驗的讀者都能知道怎麼做。BEC遭受的整數溢位的攻擊原理非常簡單,但是在攻擊手段被披露的24小時內就有30多個合約被攻擊,這也不得不引起我們的重視和思考:合約本身並不具備安全的屬性,卻動輒承載上千萬價值的代幣,我們過往對於合約的安全評估是否過於樂觀?