作者:LoRexxar’@知道創宇404區塊鏈安全研究團隊
時間:2018年9月6日
系列文章:
一、簡介
在知道創宇404區塊鏈安全研究團隊整理輸出的《知道創宇以太坊合約審計CheckList》中,把“溢出問題”、“重入漏洞”、“權限控制錯誤”、“重放攻擊”等問題統一歸類為“以太坊智能合約編碼安全問題”。
“昊天塔(HaoTian)”是知道創宇404區塊鏈安全研究團隊獨立開發的用於監控、掃描、分析、審計區塊鏈智能合約安全自動化平台。我們利用該平台針對上述提到的《知道創宇以太坊合約審計CheckList》中“以太坊智能合約編碼安全”類問題在全網公開的智能合約代碼做了掃描分析。詳見下文:
二、漏洞詳情
1、溢出問題
以太坊Solidity設計之初就被定位為圖靈完備性語言。在solidity的設計中,支持int/uint變長的有符號或無符號整型。變量支持的步長以8遞增,支持從uint8到uint256,以及int8到int256。需要注意的是,uint和int默認代表的是uint256和int256。uint8的數值範圍與C中的uchar相同,即取值範圍是0到2^8-1,uint256支持的取值範圍是0到2^256-1。而當對應變量值超出這個範圍時,就會溢出至符號位,導致變量值發生巨大的變化。
(1) 算數溢出
在Solidity智能合約代碼中,在餘額的檢查中如果直接使用了加減乘除沒做額外的判斷時,就會存在算術溢出隱患
contract MyToken { mapping (address => uint) balances; function balanceOf(address _user) returns (uint) { return balances[_user]; } function deposit() payable { balances[msg.sender] += msg.value; } function withdraw(uint _amount) { require(balances[msg.sender] - _amount > 0); // 存在整數溢出 msg.sender.transfer(_amount); balances[msg.sender] -= _amount; } }
在上述代碼中,由於沒有校驗_amount
一定會小於balances[msg.sender]
,所以攻擊者可以通過傳入超大數字導致溢出繞過判斷,這樣就可以一口氣轉走巨額代幣。
2018年4月24日,SMT/BEC合約被惡意攻擊者轉走了50,659,039,041,325,800,000,000,000,000,000,000,000,000,000,000,000,000,000,000個SMT代幣。惡意攻擊者就是利用了SMT/BEC合約的整數溢出漏洞導致了這樣的結果。
2018年5月19日,以太坊Hexagon合約代幣被公開存在整數溢出漏洞。
(2) 鑄幣燒幣溢出問題
作為一個合約代幣的智能合約來說,除了有其他合約的功能以外,還需要有鑄幣和燒幣功能。而更特殊的是,這兩個函數一般都為乘法或者指數交易,很容易造成溢出問題。
function TokenERC20( uint256 initialSupply, string tokenName, string tokenSymbol ) public { totalSupply = initialSupply * 10 ** uint256(decimals); balanceOf[msg.sender] = totalSupply; name = tokenName; symbol = tokenSymbol; }
上述代碼未對代幣總額做限制,會導致指數算數上溢。
2018年6月21日,Seebug Paper公開了一篇關於整數溢出漏洞的分析文章ERC20 智能合約整數溢出系列漏洞披露,裡面提到很多關於指數上溢的漏洞樣例。
2、call注入
Solidity作為一種用於編寫以太坊智能合約的圖靈完備的語言,除了常見語言特性以外,還提供了調用/繼承其他合約的功能。在call
、delegatecall
、callcode
三個函數來實現合約之間相互調用及交互。正是因為這些靈活各種調用,也導致了這些函數被合約開發者“濫用”,甚至“肆無忌憚”提供任意調用“功能”,導致了各種安全漏洞及風險。
function withdraw(uint _amount) { require(balances[msg.sender] >= _amount); msg.sender.call.value(_amount)(); balances[msg.sender] -= _amount; }
上述代碼就是一個典型的存在call注入問題直接導致重入漏洞的demo。
2016年7月,The DAO被攻擊者使用重入漏洞取走了所有代幣,損失超過60億,直接導致了eth的硬分叉,影響深遠。
2017年7月20日,Parity Multisig電子錢包版本1.5+的漏洞被發現,使得攻擊者從三個高安全的多重簽名合約中竊取到超過15萬ETH ,其事件原因是由於未做限制的 delegatecall 函數調用了合約初始化函數導致合約擁有者被修改。
2018年6月16日,「隱形人真忙」在先知大會上分享了「智能合約消息調用攻防」的議題,其中提到了一種新的攻擊場景——call注⼊,主要介紹了利用對call調用處理不當,配合一定的應用場景的一種攻擊手段。接着於 2018年6月20日,ATN代幣團隊發布「ATN抵禦黑客攻擊的報告」,報告指出黑客利用call注入攻擊漏洞修改合約擁有者,然後給自己發行代幣,從而造成 ATN 代幣增發。
2018年6月26日,知道創宇區塊鏈安全研究團隊在Seebug Paper上公開了《以太坊 Solidity 合約 call 函數簇濫用導致的安全風險》。
3、權限控制錯誤
在智能合約中,合約開發者一般都會設置一些用於合約所有者,但如果開發者疏忽寫錯了函數權限,就有可能導致所有者轉移等嚴重後果。
function initContract() public { owner = msg.reader; }
上述代碼函數就需要設置onlyOwner。
4、重放攻擊
2018年,DEFCON26上來自 360 獨角獸安全團隊(UnicornTeam)的 Zhenzuan Bai, Yuwei Zheng 等分享了議題《Your May Have Paid More than You Imagine:Replay Attacks on Ethereum Smart Contracts》
在攻擊中提出了智能合約中比較特殊的委託概念。
在資產管理體系中,常有委託管理的情況,委託人將資產給受託人管理,委託人支付一定的費用給受託人。這個業務場景在智能合約中也比較普遍。
這裡舉例子為transferProxy函數,該函數用於當user1轉token給user3,但沒有eth來支付gasprice,所以委託user2代理支付,通過調用transferProxy來完成。
function transferProxy(address _from, address _to, uint256 _value, uint256 _fee, uint8 _v, bytes32 _r, bytes32 _s) public returns (bool){ if(balances[_from] < _fee + _value || _fee > _fee + _value) revert(); uint256 nonce = nonces[_from]; bytes32 h = keccak256(_from,_to,_value,_fee,nonce,address(this)); if(_from != ecrecover(h,_v,_r,_s)) revert(); if(balances[_to] + _value < balances[_to] || balances[msg.sender] + _fee < balances[msg.sender]) revert(); balances[_to] += _value; emit Transfer(_from, _to, _value); balances[msg.sender] += _fee; emit Transfer(_from, msg.sender, _fee); balances[_from] -= _value + _fee; nonces[_from] = nonce + 1; return true; }
上述代碼nonce值可以被預測,而其他變量不變的情況下,可以通過重放攻擊來多次轉賬。
三、漏洞影響範圍
使用Haotian平台智能合約審計功能可以準確掃描到該類型問題。