Beatland Festival

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

No Mechanism to Recover from Organizer Key Loss

Root + Impact

Description

  • Normal Behavior:
    The FestivalPass contract uses an organizer address for critical festival operations (e.g., configuring passes, creating performances, and managing memorabilia). The contract owner can assign a new organizer using setOrganizer.

    Issue:
    If the organizer’s private key is lost or compromised, all organizer-only functions become permanently inaccessible or vulnerable to abuse. The only way to recover is for the contract owner to call setOrganizer and assign a new organizer address. However, if the owner’s key is also lost or compromised, there is no recovery mechanism—organizer privileges are lost forever, and the contract becomes permanently locked for all organizer-only actions.

address public organizer;
function setOrganizer(address _organizer) external onlyOwner {
@> organizer = _organizer;
}
modifier onlyOrganizer() {
@> require(msg.sender == organizer, "Only organizer can call this");
_;
}

Risk

Likelihood:

  • Key loss or compromise is a common operational risk, especially if EOAs are used.

Impact:

  • If both the organizer and owner keys are lost, all organizer-only functionality is permanently disabled, bricking the contract for its intended use.

Proof of Concept

  1. Organizer loses access to their private key.

  2. No one can call organizer-only functions (e.g., configurePass).

  3. If the owner is also lost or compromised, no one can call setOrganizer to recover.

  4. The contract is permanently locked for all organizer operations.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import "forge-std/Test.sol";
import "../src/FestivalPass.sol";
import "../src/BeatToken.sol";
contract FestivalPassKeyLossTest is Test {
FestivalPass festival;
BeatToken beat;
address owner = address(0xABCD);
address organizer = address(0xBEEF);
address attacker = address(0xBAD);
function setUp() public {
beat = new BeatToken();
vm.startPrank(owner);
festival = new FestivalPass(address(beat), organizer);
vm.stopPrank();
}
function testOrganizerKeyLoss() public {
// Organizer can perform organizer-only actions
vm.startPrank(organizer);
festival.configurePass(1, 1 ether, 100);
vm.stopPrank();
// Simulate organizer key loss: attacker tries organizer-only action
vm.startPrank(attacker);
vm.expectRevert("Only organizer can call this");
festival.configurePass(1, 1 ether, 100);
vm.stopPrank();
// Owner can recover by setting a new organizer
vm.startPrank(owner);
festival.setOrganizer(attacker);
vm.stopPrank();
// Now attacker can perform organizer-only actions
vm.startPrank(attacker);
festival.configurePass(2, 2 ether, 200);
vm.stopPrank();
}
}

Recommended Mitigation

  • Use a multi-signature wallet for both owner and organizer roles to reduce the risk of key loss.

  • Consider implementing a community-driven or time-locked recovery mechanism.

- address public organizer;
- function setOrganizer(address _organizer) external onlyOwner {
- organizer = _organizer;
- }
+ address public organizer;
+ address public backupAdmin;
+
+ function setOrganizer(address _organizer) external onlyOwner {
+ organizer = _organizer;
+ }
+
+ function setBackupAdmin(address _backup) external onlyOwner {
+ backupAdmin = _backup;
+ }
+
+ function recoverOrganizer(address newOrganizer) external {
+ require(msg.sender == backupAdmin, "Only backup admin");
+ organizer = newOrganizer;
+ }
Updates

Lead Judging Commences

inallhonesty Lead Judge 27 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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