重入攻击的智能合约良好实践



我是一个从事solidity和区块链技术的菜鸟,我正在阅读一些很好的实践来改进我的代码。

我有一个关于代码的问题,我不太理解:

资料来源: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。此外,提款通过使用 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.value userBalances[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,无法进一步提款。

最新更新