Ethernaut: Fallback
Overview
I recently launched a blog post series where I will be posting about my journey in Web3/Blockchain security.
During this series, I will be posting about Web3/Blockchain bugs and exploits, alongside writeups for Ethernaut and Damn Vulnerable DeFi challenges.
You can check the first ‘Hello Ethernaut’ Ethernaut challenge here.
Today, I am following up on the Ethernaut challenges series presented to us by OpenZeppelin !
Hope you enjoy it and learn something new!
‘Fallback’ Challenge

Overview
Fallback is the second challenge in the Ethernaut series.
Link to the original challenge.
Github repo link that contains challenge code and solver.
Challenge Description
1 | Look carefully at the contract's code below. |
1 | // SPDX-License-Identifier: MIT |
Understanding the contract
Instead of solving the challenge on the browser using developer tools, I just want to do it locally this time by compiling the smart contract and interacting with it directly through unit tests. Which is the same as interacting with it on-chain.
- First we have a couple of variable declarations which are a mapping of user contributions and the owner declaration alongside a modifier declaration that will enforce that the user who interacts with specific functions is the owner:
1 | mapping(address => uint256) public contributions; |
1 | modifier onlyOwner() { |
- Next we have our constructor where we define who the owner is (he will be the contract deployer) and provide him with initial contributions of
1000ETH. However, it is very important to note that only the contributions mapping is updated and that the contract did not actually receive ethereum to it:
1 | constructor() { |
- On the next step, we define the
contribute()method which has the logic executed when a user wants to contribute money to the contract:
1 | function contribute() public payable { |
Basically this method requires the amout of ETH being sent to be superior to 0.001 then it increases the contribution of the sender. If the sender’s contributions become superior to those of the owner (which is 1000ETH ), he becomes the owner.
- We have a withdraw function that is only accessible to the owner and allows him to exfiltrate all the funds from the contract to himself:
1 | function withdraw() public onlyOwner { |
- We also have a
receive()function, which is the most interesting in this contract, the receive() function is a special function in designed to handle plain Ether transfers sent directly to a contract address without any accompanying data. It is invoked specifically when Ether is sent using methods likesend,transfer, orcallwith empty calldata. In our case, it checks if the amount sent is superior to 0 and if the sender’s contributions are not null. In that case, it will make the sender into the owner of the contract:
1 | receive() external payable { |
Point of failure
- The
receive()method fails miserabely here: If any user contributes to the contract AND sends ETH without any calldata, it will make him owner and then he will be able to withdraw all the funds from the contract by abusing thewithdraw()function.
Exploitation
We have 2 objectives in the Fallback challenge:
- Become owner of the contract.
- Withdraw all the funds from the contract.
Exploitation Steps
As usual for the Ethernaut challenges series, I decided to use Hardhat for PoC coding.
- First, we will need to give our attacker some ETH and make a contribution to the contract:
1 | await networkHelpers.setBalance(attacker.address, ethers.parseEther("1")); |
- Next, we will send direct ETH to the contract to trigger the
receive()method:
1 | await attacker.sendTransaction({to: await fallback.getAddress(),value: ethers.parseEther("0.0001"),}); |
- Then, we can check if the attacker has become owner of the contract, and indeed he did:
1 | if (expect(await fallback.owner()).to.equal(attacker)){ |
- And finally, we can withdraw all the funds from the contract and make sure it is left with 0 ETH:
1 | await fallbackAsAttacker.withdraw(); |
And that way, we have achieve all the necessary objectives.
Exploit test case
You can find below the full Proof of Concept:
1 | import { expect } from "chai"; |
And below is the output showcasing our successful exploitation:
1 | $ npx hardhat test |
Conclusion
We can conclude from what has been elaborated above that including contract logic that might tamper with the invariants of the code inside of the receive() function can be really dangerous, especially when it is related to transferring ownsership of the contract.
That was it for Fallback challenge from Ethernaut series.
You can find through this github link the repository that contains my solver and all the future Ethernaut solutions Inshallah!
See you next time~
- Title: Ethernaut: Fallback
- Author: Foued SAIDI
- Created at : 2025-12-22 20:46:49
- Updated at : 2025-12-22 20:48:03
- Link: https://kujen5.github.io/2025/12/22/Ethernaut-Fallback/
- License: This work is licensed under CC BY-NC-SA 4.0.