解析SMT溢出漏洞代码分析及攻击演示

漏洞概述

SmartMesh Token是基于Ethereum的合约代币,简称SMT。Ethereum是一个开源的、公共的分布式计算平台,SmartMesh代币合约SmartMeshTokenContract基于ERC20Token标准。漏洞发生在转账操作中,攻击者可以在无实际支出的情况下获得大额转账。

漏洞影响

不久前,BEC美蜜遭遇黑客的毁灭性攻击,天量BEC从两个地址转出,引发了市场抛售潮。当日,BEC的价值几乎归零。

4月25日早间,火币Pro公告,SMT项目方反馈今日凌晨发现其交易存在异常问题,经初步排查,SMT的以太坊智能合约存在漏洞。受此影响,火币Pro现决定暂停所有币种的充提币业务。

截至目前,OKEx已经暂停使用ERC-20 token的取款交易。

解析SMT溢出漏洞代码分析及攻击演示_第1张图片

交易记录如下图:

交易记录地址:https://etherscan.io/tx/0x0775e55c402281e8ff24cf37d6f2079bf2a768cf7254593287b5f8a0f621fb83

解析SMT溢出漏洞代码分析及攻击演示_第2张图片

可以看出交易了天量的代币,超出了发行总量,有点搞笑。

这个问题是怎么造成的呢,如何再现一下我们自己写一个小小的实例,讲解一下。

解析SMT溢出漏洞代码分析及攻击演示_第3张图片

溢出的原理就是这样,我们来分析下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的合约恶意攻击都是通过恶意的整数溢出来绕过条件检查。目前以太坊上运行着上千种合约,这上千种合约中可能也存在类似的安全隐患,所以作为合约的开发人员需要投入更多的精力来确保合约的安全性。


你可能感兴趣的:(区块链,以太坊)