Ethernaut L1: Fallback Function

30/01/2022



To overcome this challenge, what we must do are two things:

     1. Make us the owner of the contract.

     2. Reduce the contract balance to 0.

To do this, we are going to make use of the contract's fallback function.


What is the fallback function

The fallback function of a contract gives it the inherent ability to act as a wallet, meaning that other wallets or contracts can send you ether knowing only his public address (and without knowing his ABI or methods to receive ethers).
(We'll see later that a contract without a backup feature can still receive ethers if another contract makes use of a feature called self-destruct.)

Fallback fuction properties:

1. Has no name or arguments.
2. If it is not marked payable, the contract will throw an exception if it receives plain ether without data.
3. Can not return anything.
4. Can be defined once per contract.
5. It is also executed if the caller meant to call a function that is not available.
6. It is mandatory to mark it external.
7. It is limited to 2300 gas when called by another function. It is so for as to make this function call as cheap as possible.


A good development and security practice is to keep this function as simple as possible.

Reading the challenge contract

The purpose of the contract is to define the owner as the address that deposited the most ethers in it.

Throughout this series of posts we are going to read the code of the proposed contract line by line, trying to understand what it does and how it does it to detect existing vulnerabilities.

The first thing we find when we read the FallBack contract are 2 public state variables, one called "contributions", which is a mapping (it can be seen as a key-value dictionary data type), where for each address there corresponds a quantity in uint, and another of type address called "owner", which refers to the owner of the contract.

Since the owner variable is public, we can use its getter function to find out its value from the console:


We can know that this owner is the one who deployed the contract, since the owner variable is defined in the constructor as the msg.sender:

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


We also see that an amount of 1000 ethers is assigned to the owner.

As we can see, we have two ways to become the owner of the contract: One is by calling the contribute() function and sending more than 1000000000000000000000 wei = 1000 ethers. The other way is by using the fallback function.

Analyzing the fallback function of the contract

At the beginning of the function we find the following line:

require(msg.value > 0 && contributions[msg.sender] > 0);

This means that the fallback function needs two things to happen in order not to revert:
1. That the amount we send to the contract (msg.value) is greater than 0.
2. That the address that sends the ethers (msg.sender) already has ethers previously deposited in the contract.

Then, if these requirements are exceeded, the following line is executed:

owner = msg.sender;

That is, it defines the owner as the address that called the fallback function.

Solving the challenge

Con esto vemos que para superar el desafío lo que debemos hacer es lo siguiente:

1. We send ethers to the contract (any amount) to satisfy the requirement of contributions[msg.sender] > 0, we can do this by calling the contribute() function, which is public and payable. It is important to send an amount less than 0.001 ether, otherwise the function will revert (since it is coded that way).

await contract.contribute({value: 1});

We can see that we actually deposited the ether by calling the getContribution() function  



2. Now what we can do is send an amount greater than 0 ethers to the contract, we can use the function of the Web3 library called sendTransaction(), which by having metamask we can use it in our browser console.

await contract.sendTransaction({value: 1});

What this does, as we have seen, is to execute the fallback function of the contract, this function verifies that the amount we send is greater than 0 (it is) and that our address already has ethers deposited (we already have it, in the previous function we send 1 gwei).

As we can see, we are now the owner of the contract:


3. The last thing we must do is leave the contract balance at 0, for this we can call the withdraw() function which will be executed only if it is called by the owner of the contract (which we are now). What this function does is transfer the entire balance of the contract address to the address of the contract owner.

We can see the balance of the contract address using the getBalance method: 
await getBalance(contract.address);

And ready! If the transactions were verified correctly, we are now the owner of the contract and the balance of the contract has been transferred to our address.