This is a in-depth series around Zeppelin team’s smart contract security puzzles. I’ll give you the direct resources and key concepts you’ll need to solve the puzzles 100% on your own.
This levels requires you to exploit a poorly implemented
fallback function to gain control of someone else’s smart contract.
What is a Fallback function
It is best practice to implement a simple
Fallback function if you want your smart contract to generally receive Ether from other contracts and wallets.
The Fallback function enables a smart contract’s inherent ability to act like a wallet.
If I have your wallet address, I can send you Ethers without your permission. In most cases, you might want to enable this ease-of-payment feature for your smart contracts too. This way, other contracts/wallets can send Ether to your contract, without having to know your ABI or specific function names.
Note: without a fallback, or known payable functions, smart contracts can only receive Ether: i) as a mining bonus, or ii) as the backup wallet of another contract that has self-destructed.
The problem is when developers implement key logic inside the fallback function.
Such bad practices include: changing contract ownership, transferring the funds, etc. inside the fallback function:
This level demonstrates how you open up your contract to abuse, because anyone can trigger a fallback function.
Ways to trigger the Fallback function
Anyone can call a fallback function by:
- Calling a function that doesn’t exist inside the contract, or
- Calling a function without passing in required data, or
- Sending Ether without any data to the contract
There are two places inside Fallback.sol where, as the
msg.sender, you can become the contract’s owner:
The first option requires you to send
1000000000000000000000 wei, or
1000 Ether to this smart contract.
You probably don’t have ~5 hours to slowly request 1000 ethers from Ropsten faucet, which heavily throttles you after the first few requests. So let’s fallback to the
Notice the fallback function has two requirements:
require(msg.value > 0 && contributions[msg.sender] > 0);
- Your account address needed to have donated Ether to this contract in the past
- Your winning fallback function call needs to contain some Ether value
Using Remix IDE:
- Paste the contract code into the UI. This gives Remix the matching ABI to work with.
- Make sure you are giving Remix the full import path. Ethernaut provides the short path for its dApp, which Remix does not recognize:
3. Retrieve your existing contract instance by loading the contract via the
4. Donate a nominal amount of Ether to the contract, using the
contribute function. Make sure you are donating from your
player account address.
contribute() function has the conditional statement:
require(msg.value < 0.001 ether);
Make sure your contribution
value is less than 0.001 ether.
5. Finally, add some arbitrary value into the
value field and trigger the
Inside the console, check that you now own the contract by typing
If using the console (not Remix):
You can trigger the fallback function via sending transactions through the console, to the same effect:
// Make sure you leave the "data:" field empty
Key Security Takeaways
- If you implement a fallback function, keep it simple
- Use fallback functions to emit payment events to the transaction log
- Use fallback functions to check simple conditional requirements
- Think twice before using fallback functions to change contract ownership, transfer funds, support low-level function calls, and more.
Make a smart contract do things it didn’t want to…hackernoon.com