Damn Vulnerable DeFi Challenge #2 Solution — Naive receiver
Throughout numerous challenges, you will build the skills to become a bug hunter or security auditor in the space.
Challenge #2 — Naive receiver
There’s a lending pool offering quite expensive flash loans of Ether, which has 1000 ETH in balance.
You also see that a user has deployed a contract with 10 ETH in balance, capable of interacting with the lending pool and receiveing flash loans of ETH.
Drain all ETH funds from the user’s contract. Doing it in a single transaction is a big plus ;)
The attacker end goal
Our end goal here is to attack the user contract and drain all their funds. Draining doesn’t mean necessarily stealing those funds, it could simply mean, like in this case, to move user’s funds from their contract without their will.
Study the contracts
The contract is a lending pool that allows flash loans with a fixed fee of 1 ether (as the comment says, it’s not so cheap!).
What does it mean? That after doing a flash loan we must repay our debt plus 1 ether.
Let’s look at the
flashLoan function that takes two parameters:
borrowerthat is the smart contract address that will receive the borrow
borrowAmountthe amount of ether that will be sent to the
The function will:
- Check that the balance of the contract is greater than the requested borrowed amount
- That the borrower is a contract and not an EOA. This is needed because the lending pool is going to send the borrow amount calling a specific callback that must be implemented by the contract.
receiveEtheron the borrower with the fee amount as the parameter. The amount is sent with the receiveEther callback using
functionCallWithValueutility function from OpenZeppelin’s Address library.
- After the flashloan is executed, the contract s checking that the new updated balance of the contract is greater or equal to the balance before the flashloan plus the 1 ether fee.
The contract seems fine.
This is the user’s contract that interact with the lending pool that offer the flash loan. The main function to look for is receiveEther that is the callback function called by the lending pool when the user’s request for a flash loan.
Has a single parameter called
fee that is the amount of fee that the user needs to repay the lending pool for the flash loan.
The function has some security/validation checks:
- That the
msg.senderis indeed the lending pool address the user expect to call the callback
- That the contract has enough balance to repay both the loan and the loan’s fee
- After checking that it will execute the internal logic that will benefit from the loan calling
_executeActionDuringFlashLoanand will repay the loan sending back the borrowed amount plus fee
Do you see where the problem is? While the contract’s is correctly checking that the function can be called only by the lending pool, it is not checking that the flash loan has been requested by the owner of the contract.
This mean that everyone could call the lending pool saying that the user’s contract wants to execute a flash loan. By doing that, anyone will be able to make the user’s contract pay the flash loan fee!
At this point, the solution is pretty easy. The user’s contract has 10 ethers, the flash loan fee is 1 ether, so we just need to call the 10 times in the same transaction to drain all the user’s funds from the contract.
Here are two possible solutions
You can find the full solution on GitHub, looking at NaiveReceiver.t.sol
If you want to try yourself locally, just execute
forge test — match-contract NaiveReceiverTest -vv
All Solidity code, practices and patterns in this repository are DAMN VULNERABLE and for educational purposes only.
DO NOT USE IN PRODUCTION