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 thereceiveEther()
function inside the user's FlashLoanReceiver.sol contract.
- functionCallWithValue() - This is an external call to the
FlashLoanReceiver.sol
receiveEther()
This function receives the Ether sent by the flash loan.
amountToBeRepaid
is calculated by adding the fee (1 ETH) to themsg.value
._executeActionDuringFlashLoan();
is called which is an empty dummy function.Then the
sendValue
is called on the lending pool. This function originates from theAddress.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);
.