Naive Receiver - Damn Vulnerable DeFi #02

Naive Receiver - Damn Vulnerable DeFi #02

Analysis and PoC of Damn Vulnerable DeFi Level 02 - Naive Receiver

Objectives

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.

Our objective is to empty the user's contract and drain all the ETH, and bonus points if we can do it in a single transaction.

Smart Contract Analysis

NaiveReceiverLenderPool.sol

  • flashLoan()

    This function takes the address of the borrower as input along with the borrow amount and offers a flashloan.

    • functionCallWithValue() - This is an external call to the borrower address provided by us which will call the receiveEther() function inside the user's FlashLoanReceiver.sol contract.

FlashLoanReceiver.sol

  • receiveEther()

    This function receives the Ether sent by the flash loan.

    • amountToBeRepaid is calculated by adding the fee (1 ETH) to the msg.value.

    • _executeActionDuringFlashLoan(); is called which is an empty dummy function.

    • Then the sendValue is called on the lending pool. This function originates from the Address.sol imported by the contract. It repays the loan back to the pool and the transaction is completed.

The Exploitation

The vulnerability lies in the flashLoan() function inside the lender pool. There's no validation happening inside the function to make sure that the borrower is msg.sender. Without this, any external attacker would be able to enter any user's address as the borrower and request a flash loan for them.

If the function is called once, i.e., flashLoan(FlashLoanReceiver_address, 0) it'll drain 1 Ether fee from the Receiver. To drain all the balance, we must call this function 10 times. This can be achieved by using the following code in the test case:

But this is not efficient, neither is this in a single transaction. To do this in one transaction, we can make use of loops. Here's a better solution:

This calls the flashLoan() function 10 times with 0 borrow amount, which charges a fee of 1 Ether in every flash loan call, eventually draining the flash loan receiver.

Run the script using yarn run naive-receiver and the test case will pass.

Key Takeaways

Input validation and access control should be the utmost priority when implementing any sensitive function dealing with tokens or Ether. The attack would have been prevented had there been a validation on the borrower's address like require(borrower == msg.sender);.