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

Smart contract good practice for reentrancy attacks

我是一名使用 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)());行被执行时,它运行是fallback函数中的代码。此时,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,无法再提现。