天天看點

【轉載】智能合約安全事故回顧(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多個合約被攻擊,這也不得不引起我們的重視和思考:合約本身并不具備安全的屬性,卻動辄承載上千萬價值的代币,我們過往對于合約的安全評估是否過于樂觀?