重入攻击的智能合约良好实践
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.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,无法再提现。
我是一名使用 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.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,无法再提现。