我是一个使用固态和区块链技术的新手,我正在阅读一些改进我的代码的良好实践。
还有一个关于我不太了解的代码的问题:
来源: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所需的时间是我们想要的。关于这个问题,我的问题是,恶意代理如何调用该滥用代码并多次调用该代码行。我显然在这里丢失了一些东西。
答案 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.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,将无法进一步取款。