My Avatar

毕竟话少

业精于勤荒于嬉,行成于思毁于随!

智能合约审计-返回值检查

2020年3月9日 星期一, 发表于 合肥

如果你对本文有任何的建议或者疑问, 可以在 这里给我提 Issues, 谢谢! :)

描述:未检查低级别调用的返回值,在solidity中的低级别调用与其他函数调用不同,如果调用中发生了异常并不会将异常传递,而只是返回true或false。因此程序中必须对低级别调用的返回值进行检查,而不能期待其出错后促使整个调用回滚。

核心问题:对低级别调用的函数没有对返回值进行检查。

概念

低级别调用(Low Level Calls)包含:

低级别调用与普通函数调用(contract call)的区别:

引发问题:对于低级别的调用,如果不对返回值进行检验,将不能获知低级别调用的结果

低级别调用中产生异常的原因:

漏洞合约分析

pragma solidity ^0.4.24;

contract UncheckedGame{
    
    uint etherLeft=0;
    mapping (address => uint256) public balances;

    event Deposite(address _who, uint _amount);
    event Withdraw(address _who, uint _amount);

    function deposite() public payable returns (uint){
        balances[msg.sender]+=msg.value;
        etherLeft+=msg.value;
        emit Deposite(msg.sender, msg.value);
        return balances[msg.sender];
    }

    function withdraw(uint256 _amount) public {
        require(balances[msg.sender] >= _amount);
        msg.sender.send(_amount);
        balances[msg.sender] -= _amount;
        etherLeft -= _amount;
        emit Withdraw(msg.sender, _amount);
    }
    
    function ownedEth() public constant returns(uint256){
        return this.balance;
    }
}

contract revertContract{
    
    function testDeposite(address _addr) public payable{
        bytes4 methodHash = bytes4(keccak256("deposite()"));
        _addr.call.value(msg.value)(methodHash);
    }
    
    function testWithdraw(address _addr, uint256 _amount) public payable{
        bytes4 methodHash = bytes4(keccak256("withdraw(uint256)"));
        _addr.call(methodHash,_amount);
    }
    
    function ownedEth() public constant returns(uint256){
        return this.balance;
    }
    
    function() public payable{
        revert();
    }
}

漏洞点:在提币的时候使用可send()低级别调用函数,在转账的过程中没有对返回值进行检查,致使下一行的balances[msg.sender] -= _amount代码继续执行,导致金额未转账成功,但余额被扣除的现象。


使用Remix进行进行调试

漏洞预防

  1. 对于任意的低级别调用,需要检验调用的返回值,并做出对应的反馈
  2. 如果仅仅是eth转账,改用transfer()而不是send()