zoukankan      html  css  js  c++  java
  • "Unchecked-Send"漏洞分析

    author:sf197
    tl;dr
    国内并没有一个文档有讲述该漏洞的,正好闲着没事。就写下这篇文章。在网上也搜寻了一些资料,通过自己的翻译才有今天的这篇文章。该漏洞在DASP TOP 10中可以查看到。至于查看的资料,将会在文章末尾贴上。转载请注明作者及文章来源。

    什么是“未检查发送”漏洞?
    简洁来说,就是一个或多个Ether发送到其他地址时,其他合约拒绝,或发生错误时,会返回一个布尔值false,并且代码将会继续。
    倘若未给这些返回值做检测,可能会造成意想不到的结果。

    漏洞函数: call()、 callcode()、 delegatecall()、 send();

    这里我们用send()函数做一次测试。
    测试代码如下:


    1. pragma solidity ^0.4.19;

    2. contract test
    3. {
    4. bool etherLeft;
    5. mapping (address => uint256) balances;

    6. function Deposit(address addrs,uint256 _value)
    7. public
    8. payable
    9. {
    10. balances[addrs]+=_value;
    11. }


    12. function withdraw(address addr,uint256 _amount) public returns(bool etherLeft)
    13. {
    14. require(balances[addr] >= _amount);
    15. balances[addr] -= _amount;
    16. if(addr.send(_amount)){
    17. etherLeft = true;
    18. }else{
    19. etherLeft = false;
    20. }
    21. return etherLeft;
    22. }

    23. function balanceOf(address _owner) constant returns (uint256 balance) {
    24. return balances[_owner];
    25. }
    26. }

    首先我们利用Deposit函数给自己合约地址充值,再利用send()向合约地址转账。

     

     


    可以看到这里转账的结果是false
    这里其实有个坑,官方给出send函数关联fallback函数是必须的!

    1. Contracts that receive Ether directly (without a function call, i.e. using send or transfer) but do not define a fallback function throw an exception, sending back the Ether (this was different before Solidity v0.4.0). So if you want your contract to receive Ether, you have to implement a fallback function.

    换句话说,通过send,transfer这些函数转移代币的时候,若被接收方是合约的话,则该合约必须存在fallback函数
    我们就正好利用这个情况,复现我们的未检测发送漏洞。
    代码如下:

    1. pragma solidity ^0.4.19;

    2. contract test
    3. {
    4. mapping (address => uint256) balances;

    5. function Deposit(address addrs,uint256 _value)
    6. public
    7. payable
    8. {
    9. balances[addrs]+=_value;
    10. }


    11. function withdraw(address addr,uint256 _amount) public
    12. {
    13. require(balances[addr] >= _amount);
    14. addr.send(_amount);
    15. balances[addr] -= _amount;
    16. }

    17. function balanceOf(address _owner) constant returns (uint256 balance) {
    18. return balances[_owner];
    19. }
    20. }

    上述代码进过修改后,写合约的人逻辑是,先转账_amount金额,再扣除自身这么多的金额。
    然而并未在此做返回检查,致使下一行的balances[addr] -= _amount;代码继续执行。最终得到金额未转账成功,但余额中又被扣除的现象

    先给合约地址充值10代币

     

     


    再给自身合约转账5代币,由于没有fallback函数,转账会失败。

     

     

    再去查询余额,竟然变成5代币

     

    漏洞防御:
    最主要的防御是尽量避免使用send函数,因为sand函数并不是一个相对安全的函数。如若要使用,可以参考以下几种方案:
    (1)

    1. function withdraw(address addr,uint256 _amount) public returns(bool etherLeft)
    2. {
    3. require(balances[addr] >= _amount);
    4. if(addr.send(_amount)){
    5. balances[addr] -= _amount;
    6. etherLeft = true;
    7. }else{
    8. etherLeft = false;
    9. }
    10. return etherLeft;
    11. }

    就是在发送的时候做个判断,这是对当前示例的适当修复,但有时它不是正确的解决方案。
    (2)
    我们可以定义一个宏,CaltSkasiSunType()

    1. function withdraw(address addr,uint256 _amount) public returns(bool etherLeft)
    2. {
    3. require(balances[addr] >= _amount);
    4. if(callStackIsEmpty()){
    5. addr.send(_amount)
    6. balances[addr] -= _amount;
    7. }else throw;
    8. return etherLeft;
    9. }

    这种解决方案的唯一好处就是,通过CaltSkasiSunType()生成一个测试消息,来调用堆栈且当调用为堆栈为空的时,返回false。


    详细文档请看:
    http://hackingdistributed.com/2016/06/16/scanning-live-ethereum-contracts-for-bugs/#Listing4
    http://www.dasp.co/#item-4


    原文地址:http://www.rlsec.org/thread-22-1-1.html

  • 相关阅读:
    Java中的24种设计模式与7大原则
    Red Hat Enterprise Linux 6安装步骤
    jquery之获取当前时间和月份加1
    新手看过来:随车千斤顶怎么用?
    空挡滑行和带挡滑行究竟谁省油 误区要知道
    怎么开手动档轿车?这么开让你再也不想开自动档
    FindBugs插件的安装与使用
    图解Eclipse中安装和使用FindBugs
    新手开车上高速 不懂这些安全细节非常危险
    酒桌游戏
  • 原文地址:https://www.cnblogs.com/wh4am1/p/9250615.html
Copyright © 2011-2022 走看看