SmartMesh Token是基于Ethereum的合约代币,简称SMT。Ethereum是一个开源的、公共的分布式计算平台,SmartMesh代币合约SmartMeshTokenContract基于ERC20Token标准。漏洞发生在转账操作中,攻击者可以在无实际支出的情况下获得大额转账。
不久前,BEC美蜜遭遇黑客的毁灭性攻击,天量BEC从两个地址转出,引发了市场抛售潮。当日,BEC的价值几乎归零。
4月25日早间,火币Pro公告,SMT项目方反馈今日凌晨发现其交易存在异常问题,经初步排查,SMT的以太坊智能合约存在漏洞。受此影响,火币Pro现决定暂停所有币种的充提币业务。
截至目前,OKEx已经暂停使用ERC-20 token的取款交易。
交易记录如下图:
交易记录地址:https://etherscan.io/tx/0x0775e55c402281e8ff24cf37d6f2079bf2a768cf7254593287b5f8a0f621fb83
可以看出交易了天量的代币,超出了发行总量,有点搞笑。
这个问题是怎么造成的呢,如何再现一下我们自己写一个小小的实例,讲解一下。
溢出的原理就是这样,我们来分析下SMT的问题:
案例分析:
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;
}
合约整数漏洞事件总结
从上面的分析中,大家可以看出针对SMT和BEC的合约恶意攻击都是通过恶意的整数溢出来绕过条件检查。目前以太坊上运行着上千种合约,这上千种合约中可能也存在类似的安全隐患,所以作为合约的开发人员需要投入更多的精力来确保合约的安全性。