我是一个从事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,无法进一步提款。