区块链安全—守株待兔的蜜罐合约

一、前言

在前面的稿件中我们更多的会去选择分析如何在已知合约中寻找存在的漏洞,并利用漏洞以达到获取非法token的目的或者利用漏洞进行作恶。

研究安全的读者应该都清楚,在进行安全防御的时候,我们除了会对已经发生的安全事件进行跟踪之外,我们还会自行设置一个陷阱,让攻击者自己掉入我们布置好的陷阱中以便能让我们更好的分析作恶者的手法。而这个陷阱又被称为蜜罐

在本文中,我们就针对智能合约的蜜罐进行分析。而这里的蜜罐不同于上面的传统的web中的蜜罐概念。在这里我们的蜜罐通常是攻击者编写的某种合约并部署在网络上,面向的对象是那些对Solidity语言不能够深入理解的一类投机用户。这些用户以为合约出现了严重漏洞就想通过合约去盗取token,然而不仅没有成功,反而把自己的本钱都赔进去了。

二、易于忽略的继承问题

1 代码分析

在讲述这个问题之前,我们首先看一个例子,代码如下:

pragma solidity ^0.4.18;

contract Owned {
    address public owner;
    function Owned() { owner = msg.sender; }
    modifier onlyOwner{ if (msg.sender != owner) revert(); _; }
}

contract TestBank is Owned {
    event BankDeposit(address from, uint amount);
    event BankWithdrawal(address from, uint amount);
    address public owner = msg.sender;
    uint256 ecode;
    uint256 evalue;

    function() public payable {
        deposit();
    }

    function deposit() public payable {
        require(msg.value > 0);
        BankDeposit(msg.sender, msg.value);
    }

    function setEmergencyCode(uint256 code, uint256 value) public onlyOwner {
        ecode = code;
        evalue = value;
    }

    function useEmergencyCode(uint256 code) public payable {
        if ((code == ecode) && (msg.value == evalue)) owner = msg.sender;
    }

    function withdraw(uint amount) public onlyOwner {
        require(amount <= this.balance);
        msg.sender.transfer(amount);
    }
}

我们先简单的对合约进行分析。

首先这里定义了一个父类合约Owned(),而在合约中我们只定义了一个构造函数与一个修饰器。

function Owned() { owner = msg.sender; }而构造函数用于将owner赋值为msg.sender。而修饰器则是为了用于判断当前调用合约的用户是否为owner

之后我们分析子合约。

子合约继承于Owned。关键函数如下:

    function setEmergencyCode(uint256 code, uint256 value) public onlyOwner {
        ecode = code;
        evalue = value;
    }

核心内容首先为设置函数,此函数要求调用函数的人为owner,并且可以设置ecode、evalue的值。

当我们将这两个值设定完成后,普通用户就可以调用useEmergencyCode()函数来完成更换owner操作。

   function useEmergencyCode(uint256 code) public payable {
        if ((code == ecode) && (msg.value == evalue)) owner = msg.sender;
    }

那用户更换完owner后会有什么好处吗?

这里我们就需要查看withdraw函数了。

    function withdraw(uint amount) public onlyOwner {
        require(amount <= this.balance);
        msg.sender.transfer(amount);
    }

这个函数为转账函数,调用此函数可以将合约中的token转账于msg.sender(不过需要转账的金额不大于此合约的余额)。也就是说如果我们能够猜对了code以及evalue的值,我们就可以更换owner了。

是不是听起来很美妙???我们似乎发现了合约中的bug。然而这就是蜜罐的核心代码。

我们分析起来是很简单的,然而我们下面进行一下测试,来看看是否跟我们分析的结果一样?

2 流程测试

首先我们在测试账户下部署TestBank合约。

然后设定这两个参数为100,100

然后我们在当前owner下向主合约存入10 eth的钱。

image.png

然后我们模拟用户进行操作,我们更换账号:0x14723a09acff6d2a60dcdf7aa4aff308fddc160c

然后尝试直接调用取钱函数:

image.png

发现调用失败,失败的原因很明显,就是因为我们的owner不是0x14723a09acff6d2a60dcdf7aa4aff308fddc160c,而是先前的0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c

此时我们用户通过对合约的分析发现了可以通过useEmergencyCode()来更改owner,此时模拟用户调用。

image.png

传入我们设定的100,100参数。并且调用成功,此时我们继续查看owner

image.png

发现owner已经被更改。用户心理窃喜,似乎我们发现了合约中存在的漏洞,并且可以调用withdraw了!!!

然而事情没有想象中那么简单。

当我的用户开开心心的去调用withdraw的时候,发现事情不妙。

image.png

用户仍然无法取出合约的钱?不仅如此,用户在调用useEmergencyCode()时传入的value也无法取出。也就是说,合约把用户的钱全部吃掉了。

至此,蜜罐的钓鱼成功,不仅用户无法取出合约的钱,连自己的钱也赔进去了。

下面我们就来分析一下这种情况发生的原因。

3 原因分析

此蜜罐能够成功的原理完全利用了用户对继承的理解不到位。关于继承,下面的参考链接中列举了详细的继承情况,并给出了分析结果。在这里我们针对本蜜罐进行分析。

继承参考链接

在继承中无非设计两种,一是将父合约完整的继承下来,第二是父类合约的函数进行修改。针对这两个情况,在EVM中有不同的对待方法。

我们来看下面的例子:

contract A{  
    uint variable = 0;  
    function test1(uint a)  returns(uint){  
       variable++;  
       return variable;  
    }  
   function test2(uint a)  returns(uint){  
       variable += a;  
       return variable;  
    }  
}  
contract B is A{  
    uint variable = 0;  
    function test2(uint a) returns(uint){  
        variable++;  
        return variable;  
    }  
}  

A中拥有variable变量,然而B中也拥有。然而对EVM来说,每个storage variable都会有一个唯一标识的slot id。虽然这两个AB中的变量名相同,但是他们有不同的slot id,也就说明他们不是同一个变量,在底层是有所区别的。

对于函数test1 ()来说,B可以完整的基础A中的这个函数,然而对于test12(),由于B将此函数修改,所以可以算做多态的感觉。也就是说A中的test2被B代替了。

于是B合约可以等价为:

contract B{  
    uint variable1 = 0;  
    uint variable2 = 0;  
    function test1(uint a)  returns(uint v){  
        variable1++;  
       return variable1;  
    }  
    function test2(uint a) returns(uint v){  
        variable2++;  
        return variable2;  
    }  
}  

也就是说我的test1test2修改的变量完全不是同一个。他们找寻的地址是不同的。

这对我们的蜜罐合约有没有启发呢?我们的蜜罐合约也为此。

由于owned合约是父合约

image.png

所以onlyOwner控制的owner为父合约的内存。也就是说此处的owner是子合约中的,而onlyOwner需要修改父类中的owner

然而我们并没有接口对其进行修改,也就意味着所有人都无法修改!

那么表面的都是虚假的,所以这个合约真正的目的就是为了骗取钱财。

三、合约更富有

下面我们再来看一个蜜罐合约。

pragma solidity ^0.4.18;

contract MultiplicatorX3
{
    address public Owner = msg.sender;
   
    function() public payable{}
   
    function withdraw()
    payable
    public
    {
        require(msg.sender == Owner);
        Owner.transfer(this.balance);
    }
    
    function Command(address adr,bytes data)
    payable
    public
    {
        require(msg.sender == Owner);
        adr.call.value(msg.value)(data);
    }
    
    function multiplicate(address adr)
    public
    payable
    {
        if(msg.value>=this.balance)
        {        
            adr.transfer(this.balance+msg.value);
        }
    }
}

与往常一样,我们首先需要对这个合约进行一个简单的分析。

这个合约有个withdraw()函数,这个函数用于使合约的拥有者将合约的所有余额进行提取。而这个函数目前对于我们来说无法用到,因为我们无法改变owner的值,所以对于普通用户来说是无法使用这个函数的。

下面是Command函数,这个函数同样是owner使用的。所以这里不再分析。

最重要的函数是multiplicate函数,我们来看看:

    function multiplicate(address adr)
    public
    payable
    {
        if(msg.value>=this.balance)
        {        
            adr.transfer(this.balance+msg.value);
        }
    }

在这个函数中,我们受害者首先看到函数内容就会以为:用户可以调用此函数,并赋值value一个大于合约余额的数,然后就会满足if条件,之后合约就会向adr地址进行转账操作。

然而真正的情况会有用户所想的这么美好吗?我们来做个实验。

首先我们将合约部署:

image.png

为了方便我们查看合约余额,我们写入查看余额的函数。

image.png

此时余额为0,并可以查看到owner的地址。

之后我们更换账户信息为0xca35b7d915458ef540ade6068dfe2f44e8fa733c

然后我们将value的值设置为1 eth,然后调用multiplicate

image.png

此时我们注意账户的金额:

image.png

并且观察此时合约中账户的余额:

image.png

因为我们之前向合约转账了3 eth,所以此时里面有余额。

此时我们更换用户,模拟被害用户,此时用户为了投机向合约中转账4 eth。目的是收到合约转回来的7 eth。

image.png

然而调用函数后我们发现账户余额只减少却没有增加。

image.png

再次查看合约发现其内部金额只增加反而没有减少。用户的钱白白成为了“战利品”。

image.png

根据我们的实验,我们发现,代码中的this.balance原来的余额+value,而此处的msg.value>=this.balance可以等价为msg.value>=this.originBalance + msg.value,所以是用户不可能满足的。

这也就是用户对solidity语法了解的不清楚导致的,应该引以为戒。

四、参考文献

首发于先知:[https://xz.aliyun.com/t/3963](https://xz.aliyun.com/t/3963)

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

推荐阅读更多精彩内容