前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >智能合约安全审计之路-返回值检查漏洞

智能合约安全审计之路-返回值检查漏洞

作者头像
字节脉搏实验室
发布2020-03-31 10:11:39
1.1K0
发布2020-03-31 10:11:39
举报

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

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

概念

低级别调用(Low Level Calls)包含:
  • call():发出低级别调用,若发生异常则返回false
  • callcode():发出低级别调用(类似代码注入),若发生异常则返回false
  • delegatecall():与callcode的区别在与msg指向不同
  • send():发送指定数量的wei到这个地址,若发生异常则返回false
低级别调用与普通函数调用(contract call)的区别:
  • 普通调用中抛出异常事,异常会沿着函数调用栈向上传递
  • 低级别调用中抛出异常,仅会返回false

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

低级别调用中产生异常的原因:
  • 代码中主动revert()
  • gas不足
  • 超过了1024调用栈深度

漏洞合约分析

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进行进行调试

  • 首先对合约进行编译(Current version设置为0.4.24,Auto compile,Enable Optimization全部勾上。编译完成后会出现2个合约分别为UncheckedGame、revertContract,并部署
  • 首先使用UncheckedGame合约对revertContract合约进行充值(0x089…59fb) 100wei ETH,点击testDeposite进行充值。点击ownedETH可以查看到已经充值100wei ETH
  • 然后使用revertContract合约的testWithdraw函数进行提币20wei ETH(_amount设置为20)点击transact
  • 转账完成后点击balances(0x5c3…05b07)查看余额为80wei ETH,然后查看revertContract合约的onwedETH余额并未增加,实验成功。

漏洞预防

  1. 对于任意的低级别调用,需要检验调用的返回值,并做出对应的反馈
  2. 如果仅仅是eth转账,改用transfer()而不是send()
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 字节脉搏实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概念
  • 漏洞合约分析
  • 使用Remix进行进行调试
  • 漏洞预防
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档