关于再入攻击的智能合约良好做法

时间:2018-08-07 10:36:23

标签: ethereum solidity reentrancy remix consensys-truffle

我是一个使用固态和区块链技术的新手,我正在阅读一些改进我的代码的良好实践。

还有一个关于我不太了解的代码的问题:

来源https://github.com/ConsenSys/smart-contract-best-practices/blob/master/docs/known_attacks.md

// INSECURE
mapping (address => uint) private userBalances;

function withdrawBalance() public {
    uint amountToWithdraw = userBalances[msg.sender];
    require(msg.sender.call.value(amountToWithdraw)()); // At this point, the caller's code is executed, and can call withdrawBalance again
    userBalances[msg.sender] = 0;
}

在上面的代码中被认为是不安全的,因为恶意代理可以调用​​步骤2所需的时间是我们想要的。关于这个问题,我的问题是,恶意代理如何调用该滥用代码并多次调用该代码行。我显然在这里丢失了一些东西。

1 个答案:

答案 0 :(得分:2)

这被称为再入攻击。

这是不安全的,因为仅在处理提款后,用户的余额才设置为0。此外,提款是使用evm的CALL操作码处理的,它将控制权传递给接收地址。

如果接收地址是合同,则可以使用后备功能劫持此转移。在此后备功能内,它可以检查发送合同的余额是否超过已转移的金额。如果是这样,它将再次调用withdrawBalance,直到提取合同的余额用尽为止。

简单的攻击者合同可能类似于:

contract Attacker {

    function startattack() {
        victim.withdrawBalance();
    }

    function() payable {
        if (victim.balance > msg.value) {
            victim.withdrawBalance();
        }
    }
}

通过致电startattack,您可以提款。执行require(msg.sender.call.value(amountToWithdraw)());行时,它将运行后备功能中的代码。此时,msg.valueuserBalances[msg.sender]。攻击者可以检查受害人的合同是否还有比userBalances[msg.sender]多的以太币,然后再次进行提款(这将导致该过程循环进行,直到余额下降到userBalances[msg.sender]以下)。

可以通过将行的顺序切换为:

来避免这种情况。
function withdrawBalance() public {
    uint amountToWithdraw = userBalances[msg.sender];
    userBalances[msg.sender] = 0;
    require(msg.sender.call.value(amountToWithdraw)());
}

现在,即使攻击者再次致电withdrawBalance,用户的余额也已设置为0,将无法进一步取款。