事件回顾
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%。
该漏洞代理的直接经济损失高达上亿元人民币,间接产生的负面影响目前无法估量。这到底是怎样一个漏洞呢?下面将详细分析该漏洞的产生和解决方案。
漏洞分析
SMT与前几天爆出的美图BEC代币都出现类似的安全漏洞—整数溢出,那么什么是整数溢出,整数溢出出现的原因是什么,怎样才能避免整数溢出呢?接下来我们带着这些问题来看下面的内容。
整数溢出
整数溢出分向上溢出和向下溢出,有关智能合约安全的其他关键点作者在《区块链开发实战——以太坊关键技术与案例分析》中有详细介绍,以下是截取本书中关于整数溢出的部分,通过下面文字的阅读大家就可以对:什么是整数溢出,整数溢出出现的原因是什么,怎样才能避免整数溢出呢?这三个问题有个答案了。
以下文字截取于《区块链开发实战——以太坊关键技术与案例分析》
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| pragma solidity ^0.4.10;
contract Test{
function test() returns(uint8){ uint8 a = 255; uint8 b = 1;
return a+b; }
function test_1() returns(uint8){ uint8 a = 0; uint8 b = 1;
return a-b; } }
|
有了上面的理论基础,我们在看一个转账的例子,看在我们的合约中应该如何避免不安全的代码出现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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) { if (balanceOf[msg.sender] < _value || balanceOf[_to] + _value < balanceOf[_to]) throw;
balanceOf[msg.sender] -= _value; balanceOf[_to] += _value; }
|
我们在做整数运算的时候要时刻注意上溢,下溢检查,尤其对于较小数字的类型比如uint8、uint16、uint24更加要小心:它们更加容易达到最大值,最小值。
SMT合约中的整数安全问题简析
SMT的合约地址是:0x55F93985431Fc9304077687a35A1BA103dC1e081,合约代码可以访问etherscan的如下网址进行查看
https://etherscan.io/address/0x55f93985431fc9304077687a35a1ba103dc1e081#code
SMT合约有问题的代码存在于transferProxy()函数,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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; }
|
其中的问题分析如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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; }
|
作者修改后的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| function transferProxy(address _from, address _to, uint256 _value, uint256 _feeSmt, uint8 _v,bytes32 _r, bytes32 _s) public transferAllowed(_from) returns (bool){
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();
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()函数,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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; } }
|
其中问题代码分析如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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; } }
|
攻击者发送的交易
以下是攻击者恶意发送的转账交易:
https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f
黑客攻击截图
合约整数漏洞事件总结
从上面的分析中,大家可以看出针对SMT和BEC的合约恶意攻击都是通过恶意的整数溢出来绕过条件检查。目前以太坊上运行着上千种合约,这上千种合约中可能也存在类似的安全隐患,所以作为合约的开发人员需要投入更多的精力来确保合约的安全性。
下篇我们将详细的介绍如何正确保合约的安全,敬请期待。