区块链安全—循环Dos安全分析(二)

一、前言

在上一篇文章中我们详细的分析了Simoleon合约中的一些薅羊毛等类似于Dos的安全隐患。本文我们紧接着类似攻击手法进行相关漏洞的真实合约分析。在文中,我们会针对真实的合约进行漏洞模拟测试,并在文末给出相应的解决方案。

二、代码分析

本次分析的漏洞是来源于基于以太币的一款游戏,该游戏鼓励玩家向该合约中充钱,并且根据合约owner的操作会定期的向充钱账户进行奖励(类似于分红),所以许多玩家抱着能够分得奖励的心态向合约进行投资操作。下面我们来根据代码对合约进行一些分析:

image.png

根据交易发起的时间来看,合约已经有许多天没有人光顾了。其中最主要的原因应该就是由于其内部天然的漏洞导致合约无法正常工作。

而根据截图我们知道,其账户目前还存储着价值5w+美元的以太币,而这些以太币有可能已经全部冻结在了这个合约中。

下面我们看代码:

contract Pandemica
{
    struct _Tx {
        address txuser;
        uint txvalue;
    }
    _Tx[] public Tx;
    uint public counter;
    
    address owner;
    
    
    modifier onlyowner
    {
        if (msg.sender == owner)
        _
    }
    function Pandemica() {
        owner = msg.sender;
        
    }
    
    function() {
        Sort();
        if (msg.sender == owner )
        {
            Count();
        }
    }
    
    function Sort() internal
    {
        uint feecounter;
            feecounter+=msg.value/5;
            owner.send(feecounter);
      
            feecounter=0;
       uint txcounter=Tx.length;     
       counter=Tx.length;
       Tx.length++;
       Tx[txcounter].txuser=msg.sender;
       Tx[txcounter].txvalue=msg.value;   
    }
    
    function Count() onlyowner {
        while (counter>0) {
            Tx[counter].txuser.send((Tx[counter].txvalue/100)*3);
            counter-=1;
        }
    }
       
}

这次分析的代码并不是很长,并且其原理也容易理解。

首先,合约定义了一个struct结构体,用于存储用户的地址已经对应的余额。

image.png

之后用户定义了_Tx类型的的公有数组(为了动态存储参与账户),以及uint类型的counter变量(用于保存当前_Tx[]数组的长度),已经owner(进行合约管理员的设置)。

image.png

之后为了限制用户的权限,合约定义了onlyowner修饰器。

image.png

只有函数调用方为owner时才可以调用此函数。

之后是构造函数与feedback函数:

image.png

构造函数只是单纯的讲owner初始化,而回调函数中定义了两个不同的情况。我们知道feedback函数是其他用户向合约进行转账操作时发生的,而此合约中如何受到转账操作会触发下面几个函数,首先要触发Sort(),而倘若msg.senderowner时,系统会调用Count()函数。具体内容如下:

首先我们看Count()函数:

    function Sort() internal
    {
        uint feecounter;
            feecounter+=msg.value/5;
            owner.send(feecounter);
      
            feecounter=0;
       uint txcounter=Tx.length;     
       counter=Tx.length;
       Tx.length++;
       Tx[txcounter].txuser=msg.sender;
       Tx[txcounter].txvalue=msg.value;   
    }

该函数首先定义了uint变量用于接收费用,而feecounter的值为msg.value/5。然后合约会将这五分之一的钱转给合约拥有者。之后将feecounter初始化为 。由于进行了转账操作,所以可以默认为新用户加入,所以系统会将当前全局变量counter进行更新操作(用于保存当前数组中的参与者数量),并给新用户的地址与金额做一次更新。

然而我认为此处还有逻辑设计上的错误。之后我们进行叙述。

对于count()函数:

    function Count() onlyowner {
        while (counter>0) {
            Tx[counter].txuser.send((Tx[counter].txvalue/100)*3);
            counter-=1;
        }
    }

当owner对合约进行操作的时候,进入此函数,然后当当前用户数量>=1的时候,合约就会对当前参与的所有账户进行价值为充值金额 * 0.03的奖励。

上述内容就是代码的所有逻辑。然而我们对此进行安全分析。

我们知道以太坊中是基于Gas机制而设立的。也就是说我的合约中函数的执行是需要花费相当量的gas值的。倘若我们函数过于复杂(或者是存在大量的循环操作),那么我们的函数很大概率是无法成功被执行的。

而在我们的Count()函数中就存在这个情况。不知道读者有没有发现,当我们的全局变量counter值比较小的时候此函数没有问题,然而当此函数的值变大,也就意味着while循环复杂之后,我们的函数就很有可能超过gas的限制了,也就使得交易不成功了。

于是当我们查看此合约的详细交易记录,我们发现合约中确实存在了大量的因为gas值超额而导致的交易失败:

image.png

例如下面三笔交易,此交易的交易费用从0.05一直增加到0.19,然而交易仍然是失败的。

owner在第三次用了7900000 Gas(已经接近区块Gas上限),然而仍然是失败。所以很不幸,这个合约永远无法进行此函数的使用了。

image.png

相关合约地址如下:https://etherscan.io/address/0xD8a1Db7AA1E0ec45E77B0108006dc311cD9D00e7#code

然而这个合约虽然存在漏洞,但是仍然有许多合约对其进行模仿。例如:

https://etherscan.io/address/0x4208a616fb79828ebff99f17fc472a2ad6374c72#code

/*
*
$$$$$$$$\ $$\                      $$$$$$$\                                                     $$\   $$\   $$\ $$$$$$$$\                            
$$  _____|\__|                     $$  __$$\                                                    $$ |  $$ |  $$ |$$  _____|                           
$$ |      $$\ $$\    $$\  $$$$$$\  $$ |  $$ | $$$$$$\   $$$$$$\   $$$$$$$\  $$$$$$\  $$$$$$$\ $$$$$$\ $$ |  $$ |$$ |  $$\    $$\  $$$$$$\   $$$$$$\  
$$$$$\    $$ |\$$\  $$  |$$  __$$\ $$$$$$$  |$$  __$$\ $$  __$$\ $$  _____|$$  __$$\ $$  __$$\\_$$  _|$$$$$$$$ |$$$$$\\$$\  $$  |$$  __$$\ $$  __$$\ 
$$  __|   $$ | \$$\$$  / $$$$$$$$ |$$  ____/ $$$$$$$$ |$$ |  \__|$$ /      $$$$$$$$ |$$ |  $$ | $$ |  \_____$$ |$$  __|\$$\$$  / $$$$$$$$ |$$ |  \__|
$$ |      $$ |  \$$$  /  $$   ____|$$ |      $$   ____|$$ |      $$ |      $$   ____|$$ |  $$ | $$ |$$\     $$ |$$ |    \$$$  /  $$   ____|$$ |      
$$ |      $$ |   \$  /   \$$$$$$$\ $$ |      \$$$$$$$\ $$ |      \$$$$$$$\ \$$$$$$$\ $$ |  $$ | \$$$$  |    $$ |$$$$$$$$\\$  /   \$$$$$$$\ $$ |      
\__|      \__|    \_/     \_______|\__|       \_______|\__|       \_______| \_______|\__|  \__|  \____/     \__|\________|\_/     \_______|\__|      

*     
*   https://fivepercent4ever.com
*
*   Deposit ETH and automatically receive 5% of your deposit amount daily Forever!
*
*   Each Day at 2200 UTC our smart contract will distribute 5% to all investors 
*
*   How to invest:   Just send ether to our contract address.   FivePercent4Ever will catalogue
*   your address and send 5% of your deposit every day forever. 
*
*   How to track:   check our contract address on https://etherscan.io 
*                   you will see your deposit and daily payments to your address
*                   Recommended Gas:  200000   
*                   Gas Price:        3 GWEI or more 
*   
*                   You can also visit our website at https://fivepercent4ever.com
*
*   Project Distributions:   84% for payments, 10% for advertising, 6% project administration
*
*/                                                                                                                                                   
                                                                                                                                                     


contract FivePercent4Ever
{
    struct _Tx {
        address txuser;
        uint txvalue;
    }
    _Tx[] public Tx;
    uint public counter;
    
    address owner;
    
    
    modifier onlyowner
    {
        if (msg.sender == owner)
        _
    }
    function FivePercent4Ever() {
        owner = msg.sender;
        
    }
    
    function() {
        Sort();
        if (msg.sender == owner )
        {
            Count();
        }
    }
    
    function Sort() internal
    {
        uint feecounter;
            feecounter+=msg.value/6;
            owner.send(feecounter);
      
            feecounter=0;
       uint txcounter=Tx.length;     
       counter=Tx.length;
       Tx.length++;
       Tx[txcounter].txuser=msg.sender;
       Tx[txcounter].txvalue=msg.value;   
    }
    
    function Count() onlyowner {
        while (counter>0) {
            Tx[counter].txuser.send((Tx[counter].txvalue/100)*5);
            counter-=1;
        }
    }
       
}

唯一不同的地方就是,这个合约将奖励金提高到了百分之5,但是依然存在着上述的情况发生。倘若存在恶意攻击者,那么它们很容易就可以用少额度的钱去占位,从而增加交易的gas值,已达到破坏合约的情况。

三、合约复现

下面我们对这个合约进行复现操作:

首先我们对合约进行部署。

image.png

现在我们能看到使用的是第一个账号。部署得到下面内容:

image.png

此时查看Tx[0],得到全零的信息(因为并没有用户进来)。

此时我们模拟用户转帐操作:

向合约转账0.1eth。

我们看到交易成功。

image.png

此时查看Tx[0],得到:

image.png

此时我们切换到owner账户,向合约进行转账操作:

image.png
image.png

由于上面的几次转账操作,我们得到了长度为4的数组:

下面我们模拟奖励过程:

image.png
image.png
image.png

我们模拟了6个用户进行转账操作,并且在转账后在owner账户下调用了奖励函数,得到如下内容。

image.png

大家有没有看到不同?我们的账户的钱均增加了0.0003 eth。即均增加了百分之三的金额。也就是说我们奖励成功。然而由于我的账户数量有限,无法达到超过gas的情况,所以我在这里就只进行了转账。而下面我们就可以看到具体的函数调用所花费的gas值。这里为奖金8w的gas,当然,当我们的用户数量增加后,其值肯定会增加。等到达一定的top的时候gas必然会out of。

对此,我们模拟结束。然而我们要想预防此类漏洞,最好的方法就是使用相应的函数来让用户取回自己的代币,而不是发送给对应账户,可以在一定程序上减少危害。有机会的话我会在后续的文章中讲述相关例子。

四、参考

  • https://etherscan.io/address/0xD8a1Db7AA1E0ec45E77B0108006dc311cD9D00e7#code

  • https://etherscan.io/tx/0xc6ea76e1250dffc706e0040ab75b84a78a8ba295dc6def69ea250d3a500f6c4a

  • https://bcsec.org/index/detail/id/260/tag/2

  • https://etherscan.io/address/0x4208a616fb79828ebff99f17fc472a2ad6374c72#code

本稿为原创稿件,转载请标明出处。谢谢。

原文刊载于:https://xz.aliyun.com/t/3837

你可能感兴趣的:(区块链安全—循环Dos安全分析(二))