Beatland Festival

First Flight #44
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Ownership Not Properly initialized which anyone Can Claim Ownership and Mint BEAT Tokens

Root + Impact

Description

  • Describe the normal behavior in one or more sentences


    Normally, the Ownable2Step contract ensures that only a trusted deployer (usually msg.sender) becomes the initial owner, allowing them to control access-restricted functions.


  • Explain the specific issue or problem in one or more sentences


    In this contract, ownership is not properly initialized in the constructor. The call to Ownable(msg.sender) is invalid because Ownable2Step does not accept constructor arguments. As a result, ownership remains unset, and any attacker can call transferOwnership() to become the owner, then set the festival contract and mint unlimited BEAT tokens.

constructor() ERC20("BeatDrop Token", "BEAT") Ownable(msg.sender){
} /@> Improper attempt to set ownership

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

This will occur on deployment since Ownable2Step does not accept constructor arguments to set the initial owner.

  • Reason 2

No _transferOwnership() call is made, so the contract has no owner from the start.

Impact:

  • Impact 1

Any user can call setFestivalContract() and take full control of BEAT token minting and burning.

  • Impact 2

This compromises the integrity of the token ecosystem, allowing unrestricted inflation or denial-of-service via unauthorized burns.

Proof of Concept

This Proof of Concept (PoC) showing how any attacker can become the owner and take over the setFestivalContract() function:

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;
import "forge-std/Test.sol";
import "../src/BeatToken.sol"; // Adjust path as needed
contract ExploitTest is Test {
BeatToken token;
address attacker = address(0xBEEF);
function setUp() public {
// Deploy the token contract
token = new BeatToken();
}
function test_TakeoverFestivalContract() public {
// Check that there's no owner set
assertEq(token.owner(), address(0));
// Prank as attacker
vm.startPrank(attacker);
// Attacker calls setFestivalContract to take over minting rights
token.setFestivalContract(attacker);
// Now attacker can mint tokens
token.mint(attacker, 1_000_000 ether);
assertEq(token.balanceOf(attacker), 1_000_000 ether);
vm.stopPrank();
}
}

Upon deployment, the contract has no owner set, leaving owner() as the zero address. This allows anyone to call setFestivalContract() and assign themselves as the authorized festival contract. With that, they gain unrestricted access to mint() and burnFrom(), enabling them to freely mint BEAT tokens to any address, including their own.


Recommended Mitigation


This ensures the Ownable2Step parent contract’s owner is correctly initialized, preventing unauthorized access to setFestivalContract() and any future owner-restricted functions.

- remove this code
constructor() ERC20("BeatDrop Token", "BEAT") Ownable(msg.sender) {
}
+ add this code
constructor() ERC20("BeatDrop Token", "BEAT") {
_transferOwnership(msg.sender);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 month ago
inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.