Ethernaut Walkthrough — Level 1: Fallback

Published on Dec 03, 2021

In this first level we need to try and become the owner of the contract in order to be able to draw funds from it. After inspecting the contract we can see that there is a contribute() methods, which allows us to become the owner if we contribute more than the original owner of 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;
    }
}

In the constructor we can see that the original owner has put 1000 ETH in the contract. While it's not entirely impossible to send 1000 ETH to the contract on a test netwerk, this would take up a serious amount of time calling the faucet to get free test ETH in our accounts, so that doesn't seem like a good approach. 

constructor() public {
owner = msg.sender;
  contributions[msg.sender] = 1000 * (1 ether);
}

When we take a closer look at the contract we can see that there is a fallback method defined in which the developer of the contract assigns ownership to whoever enters this method (by sending ETH to the contract itself and not to a payable function). It is required that we have done a contribution already so let's do that first.

receive() external payable {
  require(msg.value > 0 && contributions[msg.sender] > 0);
  owner = msg.sender;
}

Send a contribution that is smaller than 0.001 ETH (this is required in the contribute() method and I lost a lot of time missing that and sending more than 0.001 ETH to the method. When working in the console, we could send the money like this:

contract.contribute({value: toWei("0.0001")})

This will pop up your Metamask wallet allowing you to send the transaction. When that is succesful we now need to trigger the fallback function. To do that, simply send a small amount (larger than 0) of ETH to the contract address, directly from Metamask. When that succeeds, you will be the owner of the contract.

In a final step, we can now call the withdraw() function and drain the contract of its funds.

await contract.withdraw();

Wait for the transaction to finish and submit your instance. Well done.

Security lessons learned

The level has taught us to be careful when using fallback functions. Never write logic in a fallback method that changes contract ownership but keep things as simple as possible.

Continue from here

Here's my solution for level 2

No comments? But that’s like a Gin & Tonic without the ice?

I’ve removed the comments but you can shoot me a message on LinkedIn to keep the conversation going.