區塊鏈兄弟社區,區塊鏈技術專業問答先行者,中國區塊鏈技術愛好者彙集地git
做者:吳壽鶴,《區塊鏈開發實戰——以太坊關鍵技術與案例分析》的第一做者,《區塊鏈開發實戰——Hyperledger Fabric關鍵技術與案例分析》聯合做者,IONChain 離子鏈 首席架構師,hyperLedger核心項目開發人員,區塊鏈技術社區-區塊鏈兄弟聯合創始人。github: https://github.com/gcc2gegithub
來源:區塊鏈兄弟,國內第一家專一區塊鏈技術分享實戰的公益性媒體社區安全
原文連接:http://www.blockchainbrother.com/article/1992架構
文章發佈:請標題做者和來源,版權歸區塊鏈兄弟全部app
事件回顧:函數
4月25日早間,火幣Pro公告,SMT項目方反饋今日凌晨發現其交易存在異常問題,經初步排查,SMT的以太坊智能合約存在漏洞。火幣Pro也同期檢測到TXID爲https://etherscan.io/tx/0x0775e55c402281e8ff24cf37d6f2079bf2a768cf7254593287b5f8a0f621fb83的異常。受此影響,火幣Pro現決定暫停全部幣種的充提幣業務,隨後,火幣Pro又發佈公告稱暫停SMT/USDT、SMT/BTC和SMT/ETH的交易。此外,OKEx,gate.io等交易平臺也已經暫停了SMT的充提和交易。截止暫停交易,SMT在火幣Pro的價格下跌近20%。區塊鏈
該漏洞代理的直接經濟損失高達上億元人民幣,間接產生的負面影響目前沒法估量。這究竟是怎樣一個漏洞呢?下面將詳細分析該漏洞的產生和解決方案。測試
漏洞分析:ui
SMT與前幾天爆出的美圖BEC代幣都出現相似的安全漏洞—整數溢出,那麼什麼是整數溢出,整數溢出出現的緣由是什麼,怎樣才能避免整數溢出呢?接下來咱們帶着這些問題來看下面的內容。spa
整數溢出
整數溢出分向上溢出和向下溢出,有關智能合約安全的其餘關鍵點做者在《區塊鏈開發實戰——以太坊關鍵技術與案例分析》中有詳細介紹,如下是截取本書中關於整數溢出的部分,經過下面文字的閱讀你們就能夠對:什麼是整數溢出,整數溢出出現的緣由是什麼,怎樣才能避免整數溢出呢?這三個問題有個答案了。
如下文字截取於《區塊鏈開發實戰——以太坊關鍵技術與案例分析》
pragma solidity ^0.4.10; /** 這是一個測試整數類型上溢和下溢的例子 */ contract Test{ // 整數上溢 //若是uint8 類型的變量達到了它的最大值(255),若是在加上一個大於0的值便會變成0 function test() returns(uint8){ uint8 a = 255; uint8 b = 1; return a+b;// return 0 } //整數下溢 //若是uint8 類型的變量達到了它的最小值(0),若是在減去一個小於0的值便會變成255(uin8 類型的最大值) function test_1() returns(uint8){ uint8 a = 0; uint8 b = 1; return a-b;// return 255 } }
有了上面的理論基礎,咱們在看一個轉帳的例子,看在咱們的合約中應該如何避免不安全的代碼出現:
// 存儲用戶餘額信息 mapping (address => uint256) public balanceOf; // 不安全的代碼 // 函數功能:轉帳,這裏沒有作整數溢出檢查 function transfer(address _to, uint256 _value) { /* 檢查發送者是否有足夠的餘額*/ if (balanceOf[msg.sender] < _value) throw; /* 修改發送者和接受者的餘額 */ balanceOf[msg.sender] -= _value; balanceOf[_to] += _value; } // 安全代碼 function transfer(address _to, uint256 _value) { /* 檢查發送者是否有足夠的餘額,同時作溢出檢查:balanceOf[_to] + _value < balanceOf[_to] */ if (balanceOf[msg.sender] < _value || balanceOf[_to] + _value < balanceOf[_to]) throw; /* 修改發送者和接受者的餘額 */ balanceOf[msg.sender] -= _value; balanceOf[_to] += _value; }
咱們在作整數運算的時候要時刻注意上溢,下溢檢查,尤爲對於較小數字的類型好比uint八、uint1六、uint24更加要當心:它們更加容易達到最大值,最小值。
案例分析:
SMT合約中的整數安全問題簡析
SMT的合約地址是:0x55F93985431Fc9304077687a35A1BA103dC1e081,合約代碼能夠訪問etherscan的以下網址進行查看
https://etherscan.io/address/0x55f93985431fc9304077687a35a1ba103dc1e081#code
SMT合約有問題的代碼存在於transferProxy()函數,代碼以下:
function transferProxy(address _from, address _to, uint256 _value, uint256 _feeSmt, uint8 _v,bytes32 _r, bytes32 _s) public transferAllowed(_from) returns (bool){ if(balances[_from] < _feeSmt + _value) revert(); uint256 nonce = nonces[_from]; bytes32 h = keccak256(_from,_to,_value,_feeSmt,nonce); if(_from != ecrecover(h,_v,_r,_s)) revert(); if(balances[_to] + _value < balances[_to] || balances[msg.sender] + _feeSmt < balances[msg.sender]) revert(); balances[_to] += _value; Transfer(_from, _to, _value); balances[msg.sender] += _feeSmt; Transfer(_from, msg.sender, _feeSmt); balances[_from] -= _value + _feeSmt; nonces[_from] = nonce + 1; return true; }
其中的問題分析以下:
function transferProxy(address _from, address _to, uint256 _value, uint256 _feeSmt, uint8 _v,bytes32 _r, bytes32 _s) public transferAllowed(_from) returns (bool){ //錯誤1:這裏沒有作整數上溢出檢查 //_feeSmt,value都是由外部傳入的參數,經過咱們以前的理論這裏可能會出現整數上溢出的狀況 // 例如:_feeSmt = 8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff , // value = 7000000000000000000000000000000000000000000000000000000000000001 // _feeSmt和value均是uint256無符號整數,相加後最高位舍掉,結果爲0。 // 那麼_feeSmt + _value = 0 直接溢出,繞過代碼檢查,致使能夠構造巨大數量的smt代幣並進行轉帳 if(balances[_from] < _feeSmt + _value) revert(); uint256 nonce = nonces[_from]; bytes32 h = keccak256(_from,_to,_value,_feeSmt,nonce); if(_from != ecrecover(h,_v,_r,_s)) revert(); if(balances[_to] + _value < balances[_to] || balances[msg.sender] + _feeSmt < balances[msg.sender]) revert(); balances[_to] += _value; Transfer(_from, _to, _value); balances[msg.sender] += _feeSmt; Transfer(_from, msg.sender, _feeSmt); balances[_from] -= _value + _feeSmt; nonces[_from] = nonce + 1; return true; }
做者修改後的代碼以下:
function transferProxy(address _from, address _to, uint256 _value, uint256 _feeSmt, uint8 _v,bytes32 _r, bytes32 _s) public transferAllowed(_from) returns (bool){ //錯誤1:這裏沒有作整數上溢出檢查 //_feeSmt,value都是由外部傳入的參數,經過咱們以前的理論這裏可能會出現整數上溢出的狀況 // 例如:_feeSmt = 8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff , // value = 7000000000000000000000000000000000000000000000000000000000000001 // _feeSmt和value均是uint256無符號整數,相加後最高位舍掉,結果爲0。 // 那麼_feeSmt + _value = 0 直接溢出,繞過代碼檢查,致使能夠構造巨大數量的smt代幣並進行轉帳 // 在這裏作整數上溢出檢查 if(balances[_to] + _value < balances[_to] || balances[msg.sender] + _feeSmt < balances[msg.sender]) revert(); // 在這裏作整數上溢出檢查 ,防止交易費用 過大 if(_feeSmt + _value < _value ) revert(); // 在這裏作整數上溢出檢查 ,防止交易費用 過大 if(balances[_from] < _feeSmt + _value) revert(); uint256 nonce = nonces[_from]; bytes32 h = keccak256(_from,_to,_value,_feeSmt,nonce); if(_from != ecrecover(h,_v,_r,_s)) revert(); // 條件檢查儘可能 在開頭作 // if(balances[_to] + _value < balances[_to] // || balances[msg.sender] + _feeSmt < balances[msg.sender]) revert(); balances[_to] += _value; Transfer(_from, _to, _value); balances[msg.sender] += _feeSmt; Transfer(_from, msg.sender, _feeSmt); balances[_from] -= _value + _feeSmt; nonces[_from] = nonce + 1; return true; }
攻擊者發送的交易:
如下是攻擊者惡意發送的轉帳交易地地址:https://etherscan.io/tx/0x1abab4c8db9a30e703114528e31dee129a3a758f7f8abc3b6494aad3d304e43f
(黑客攻擊交易日誌截圖)
BEC合約中的整數安全問題簡析
BEC的合約地址是0xC5d105E63711398aF9bbff092d4B6769C82F793D,合約代碼能夠訪問etherscan的以下網址進行查看https://etherscan.io/address/0xc5d105e63711398af9bbff092d4b6769c82f793d#code
BEC合約有問題的代碼存在於batchTransfer()函數,代碼以下:
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; } }
其中問題代碼分析以下:
function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) { uint cnt = _receivers.length; // 這裏直接使用乘法運算符,可能會致使溢出 // 變量cnt爲轉帳的地址數量,能夠經過外界的用戶輸入_receivers進行控制,_value爲單地址轉帳數額,也能夠直接進行控制。 // 外界能夠經過調整_receivers和_value的數值,產生乘法運算溢出,得出非預期amount數值,amount溢出後能夠爲一個很小的數字或者0, uint256 amount = uint256(cnt) * _value; require(cnt > 0 && cnt <= 20); // 這裏判斷當前用戶擁有的代幣餘額是否大於或等於要轉移的amount數量 // 因爲以前惡意用戶經過調大單地址轉帳數額_value的數值,使amount溢出後能夠爲一個很小的數字或者0, // 因此很容易繞過balances[msg.sender] >= amount的檢查代碼。從而產生巨大_value數額的惡意轉帳。 require(_value > 0 && balances[msg.sender] >= amount); //調用Safemath庫中的安全函數來完成加減操做 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; } }
攻擊者發送的交易:
如下是攻擊者惡意發送的轉帳交易:
https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f。
(黑客攻擊交易日誌截圖)
合約整數漏洞事件總結
從上面的分析中,你們能夠看出針對SMT和BEC的合約惡意攻擊都是經過惡意的整數溢出來繞過條件檢查。目前以太坊上運行着上千種合約,這上千種合約中可能也存在相似的安全隱患,因此做爲合約的開發人員須要投入更多的精力來確保合約的安全性。
下篇咱們將詳細的介紹如何正確保合約的安全,敬請期待。
文章發佈只爲分享區塊鏈技術內容,版權歸原做者全部,觀點僅表明做者本人,毫不表明區塊鏈兄弟贊同其觀點或證明其描述