区块链安全—循环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://xz.aliyun.com/t/3837
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容