NicoleZhu

@nicolezhu

Ethernaut Lvl 1 Walkthrough: how to abuse the Fallback function

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:

Bad practice: you should not reassign contract ownership in a 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:

  1. Calling a function that doesn’t exist inside the contract, or
  2. Calling a function without passing in required data, or
  3. Sending Ether without any data to the contract

Detailed Walkthrough

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 fallback option.

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:

  1. Paste the contract code into the UI. This gives Remix the matching ABI to work with.
  2. Make sure you are giving Remix the full import path. Ethernaut provides the short path for its dApp, which Remix does not recognize:
import 'github.com/OpenZeppelin/zeppelin-solidity/contracts/ownership/Ownable.sol';

3. Retrieve your existing contract instance by loading the contract via the instance address:

Check ‘instance’ inside the console for your address. 0xb9bcfd… is my instance address.

4. Donate a nominal amount of Ether to the contract, using thecontribute function. Make sure you are donating from your player account address.

Note: the contribute() function has the conditional statement:require(msg.value < 0.001 ether);

Make sure your contribution value is less than 0.001 ether.

Check that you’re donating from your `player` wallet address

5. Finally, add some arbitrary value into the value field and trigger the (fallback) function.

Inside the console, check that you now own the contract by typingawait contract.owner();

If using the console (not Remix):

You can trigger the fallback function via sending transactions through the console, to the same effect:

contract.sendTransaction({
from: player,
value: toWei(...)
})
// 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.

More by NicoleZhu

Topics of interest

More Related Stories