智能合约整数溢出

 
在编程语言里面由算数问题导致的整数溢出是很常见的,智能合约中的 Solidity 语言中也存在整数溢出问题,类型包括三种:
 

乘法溢出
加法溢出
减法溢出

 

简单示例

 
参考: https://zhuanlan.zhihu.com/p/105471579
 
Solidity 语言中,变量支持的整数类型步长是以8递增的,从 uint8uint256, uint 默认是 uint256,以 uin8 为例
 
我们知道 uint8 是8位,我们最多可以 2**8-1,也就是 255,若是256则会造成溢出,这是上溢
 

 
 
下溢也是一样的, uint(0)-1 就是255
 
Solidity简单示例
 

pragma solidity ^0.4.25;
contract POC{
    //加法溢出
    function add_overflow() returns (uint256 _overflow){
        uint256 max = 2**256-1;
        return max+1;
    }
    //减法溢出
    function sub_overflow() returns (uint256 _underflow){
        uint256 min =0;
        return min -1 ;
        
    }
    //乘法溢出
    function mul_overflow() returns (uint256 _underflow){
        uint256 mul = 2**255;
        return mul * 2;
    }
}

加法溢出
 

 
减法溢出
 

 
乘法溢出
 

 

历史漏洞

 
参考: https://www.jianshu.com/p/1620779ee75e
 

BEC合约

 
https://etherscan.io/address/0xc5d105e63711398af9bbff092d4b6769c82f793d#code
 
漏洞位于 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;
  }

 
其中我们可控的点是 _receivers_value
 

 
_receivers 是需要转账的地址, _value 是单个账户转账的数量
 
amount 是一共需要转账的数量,直接采用乘法运算符,后面 require(_value > 0 && balances[msg.sender] >= amount); 是判断余额是否大于 amount,符合条件的话后面就进行转账
 
根据上面我们所学习的知识,我们让 cnt 为2,value2**255,就可以让 amount 为0,因此就能进行恶意转账
 
这次攻击的恶意转账记录为
 
https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f
 
可以看到向两个账号转了很多代币
 

 
本地测试一下合约是可以成功的
 

 
然后该代币市场价额瞬间归零....
 

修复方式

 
后面 balances[msg.sender] = balances[msg.sender].sub(amount); 使用了 Safemath 中的函数进行运算,因此前面的也用该库函数即可
 
uint256 amount = uint256(cnt).mul(_value);
 

 

SMT

 
合约地址: https://etherscan.io/address/0x55f93985431fc9304077687a35a1ba103dc1e081#code
 
存在溢出漏洞的合约代码是 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;
    }

 
可以看到这里面的 加减乘除 都没做安全处理,且参数都可控
 
if(balances[_from] < _feeSmt + _value) revert(); 我们可以让 feeSmt_value 相加刚好溢出为 0,从而绕过判断,因此可以让从而给to账户和msg.sender账户分别转入value和feeSmt的代币
 
这是攻击者的交易信息
 

 

FNT

 
地址为
 
https://etherscan.io/address/0x82cf44be0768a3600c4bdea58607783a3a7c51ae#code
 
漏洞位于 batchTransfers 函数
 
大致逻辑是向多地址转账,先计算总共转账的数目,然后判断是否大于转账地址的余额,之后再一个个转账
 

 
显然转账额度相加的部分存在溢出
 
我们溢出个很小的数目就可以了
 
看下攻击者的操作
 

 
所以通过上面的构造,我们成功绕过了判断,最后给两个账户分别转了2个代币和115792089237316195423570985008687907853269984665640564039457584007913129639935个(2*256-1)的代币(加起来溢出为1)
 

最后

 
Solidity 0.8.0 之后编译器内部默认集成了 SafeMath Library ,因此,在后续的版本直接使用 Solidity 默认的数学运算符 即可避免整型溢出漏洞的产生.
 

总结

 
感觉在智能合约中的整数溢出危害更大,很多币因此直接归零,所以为了防止整数溢出的发生,一方面可以在算术逻辑前后进行验证,另一方面可以直接使用 OpenZeppelin 维护的一套智能合约函数库中的 SafeMath 来处理算术逻辑.
 
同样看来,智能合约的漏洞跟传统的Web安全漏洞是大差不差的,都特别要注意来用户的输入
 

posted @ 2021-10-21 15:39  Zahad003  阅读(384)  评论(0)    收藏  举报