Ethernaut#2 - Fal1out
Ethernaut #2 - Fal1out
Claim ownership of the contract below to complete this level.
Things that might help
- Solidity Remix IDE
1 | // SPDX-License-Identifier: MIT |
Contract Breakdown
Let us breakdown the contracts to understand what each function and piece of code does. The code consists of a single contract Fallout
that is compiled with solidity version ^0.6.0.
1 | import "openzeppelin-contracts-06/math/SafeMath.sol"; |
The contract imports the OpenZeppelin SafeMath library on line 4 to address overflow and underflow issues that were prevalent in earlier versions of Solidity before version 0.5.0.
Constructor
1 | function Fal1out() public payable { |
Defining a constructor like this might look strange but in earlier versions of Solidity prior to 0.4.3, the constructor function could only be set by naming it the same as the contract, and it would be initialized upon contract deployment.
In the constructor, the owner is assigned to the msg.sender
, who is typically the contract deployer, and the owner’s allocation is set to the msg.value
.
Did you notice anything wrong with the function name? we’d get back to it soon.
Functions
In this contract, there are 4 functions and 1 function modifier. Let’s take a look at them one after the other.
1 | modifier onlyOwner { |
The onlyOwner modifier requires that the caller be the owner of the contract. When this modifier is applied to a function, it can only be executed by the contract’s owner. The requirement is enforced through a require statement that checks if the msg.sender is equal to the owner, and if not, it throws an error with the message “caller is not the owner”.
1 | function allocate() public payable { |
This function is used to set allocations of msg.sender to the amount set, msg.value
.
1 | function sendAllocation(address payable allocator) public { |
The sendAllocation
function is basically used to withdraw funds back to the corresponding allocator. The function takes in an address and checks if the allocations of that address is > 0 before transfering their balance back to them on line 3 above.
1 | function collectAllocations() public onlyOwner { |
This collectAllocations
function can only be called by the owner of the contract and this sends all of the funds in the contract to the owner.
1 | function allocatorBalance(address allocator) public view returns (uint) { |
The last function allocatorBalance
gets passed an address and returns the allocation of that address.
Solution
To hack this contract, we need to find a way to set the owner
variable to our address. Inspecting the contract, owner
is only updated once, and that is in the constructor, which is supposed to be called on contract deployment. However, the constructor function isn’t called.
WHY?
Examining the code, we can see that the contract’s name i Fallout
. However, the constructor function is named Fal1out
, with an erroneous ‘1’ instead of an ‘l.’ As a result, when the contract is deployed, the constructor function won’t be initiated, and the “owner” variable will not be set. This transforms the fal1out
function into a publicly accessible function that can be called by anyone.
To pass this challenge, all we need to do is call the fal1out
function. We can easily do this a number of ways.
Method 1.
- Copy the code to Remix and compile it.
- Select Environment and choose injected provider then select the network it was deployed on (goerli at the time of writing this).
- Load the contract from the instance address provided by inspecting the page on the Ethernaut challenge page and copy it from the console.
- Remix provides tools to interact with contracts on the left sidebar, here we can call the fal1out function directly.
Method 2.
1 | contract HackFal1out { |
Another way to externally interact with the fal1out
contract is to deploy a new contract HackFal1out
, pass it the fallout
contract address (ethernaut refers to this as instance address) and use that to initialize an instance of this contract. This instance can then be used to externally call functions on the fal1out
contract
Method 3.
We could also interact with the contract by declaring an interface IHackFallout
. This interface will consist of the Fallout
function(s) definitions we want to interact with without implementation/function body. In our case we only want to interact with the Fal1out function so our interface will look like this.
1 | Interface Fallout{ |
- Compile this interface on Remix.
- Copy the instance address from ethernaut.
- Paste into remix and load the contract at the address.
- Call the Fal1out function and confirm the transaction from your wallet.
To sum up, this is no longer considered a significant vulnerability as the definition of constructors has changed in more recent versions of Solidity (0.5.0 and above). However, it remains important to have knowledge of this issue.