Ethernaut CTF(Fallback)

Table of contents

Intro

I am currently a blockchain developer looking to transition into smart contract security. I have started the journey by doing Openzeppelins CTF's, JohnnyTimes smart contract hacking course and participating in Secureum. My goal is to complete and write about each challenge.

Challenge #1

The first challenge is you have to take ownership of the contract and reduce the balance down to 0.

Goal

We have to become the owner of this smart contract and then drain the account.

Review

I think it is a good idea to read up on what a fallback function is so that you know the capabilities of the function. Understand how to send ether when interacting with ABI, Then review the code and get an understanding of what is going on within the contract.

Fallback

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Fallback {

  mapping(address => uint) 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 (uint) {
    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;
  }
}

Fallback- is a smart contract that has very basic functionality that has two state variables.

The first state variable is a mapping which takes the address and connects it to an integer that stores this information into the contributions variable. The mapping is also public.

The next state variable is the owner that stores a payable public address and basically, this is the account that can receive funds.

Constructor- the constructor is used to kick off the contract. This is the externally owned account(EOA) or the sender that interacted with this contract. Within the constructor, we have two variables owner and contributions.

owner- is the variable that is associated with the sender that interacted with the contract. They are now the owner of this contract.

contributions- is the amount of ether that the sender is using to initialize the contract.

Modifier- The modifier used in this contract is called onlyOwner. This is a pretty common modifier. What modifiers does is protects functions from different kinds of attacks. The onlyOwner modifier can be added to a function to not only protect the function but keep you from repeating code. The OnlyOwner modifier will make sure that only the owner of the contract can call the function. This is for access control.

function contribute()- This function is a public payable function because we are sending funds to the contract.

  1. We are doing a check which is the require statement. This check is making sure that the value is greater than 0.001 ether.

  2. Next is the effect where we are gonna add the value from the sender

  3. Then we have a conditional that checks that the sender sent more than the owner, if that is the case then we make the sender the owner.

function getContribute() This function is a view function which is a free function that returns a uint. This function checks out the sender to the contributions variable.

function withDraw()- This function uses the onlyOwner modifer which requires you to be the owner to withdraw funds.

function receive()- This function is what you call a fallback function(hint at the name of this CTF). This function is used when ether is sent to the contract and not a specific function is called. The money that was sent is then sent to this function. The function has a require statement that checks that the value is greater than zero and the sender's contributions are greater than zero, if that is the case we are going to let that sender be the owner.

Solution

After understanding how this contract works. The goal is to take ownership. Understanding what a receive function and fallback function leads to getting familiar with sending transactions and how to do so. A good link for this information can be found here

This is the solution to solving the challenge

await contract.sendTransaction({ value: 3})

Next, you can check to see if you are now the owner by

await contract.owner()

You should be the new owner. Congratulations!

The last piece is to drain the account to zero. Since you are now the owner of the contract, you have permission to withdraw the funds from the account.

await contract.withdraw()

I hope this helped understand this challenge. I will be posting the next challenge soon.