59023 sc low unprotected implementation contract initializer allows unauthorized admin role assignment leading to potential governance manipulation

Submitted on Nov 7th 2025 at 20:11:48 UTC by @piyushmali for Audit Comp | Firelight

  • Report ID: #59023

  • Report Type: Smart Contract

  • Report severity: Low

  • Target: https://github.com/firelight-protocol/firelight-core/blob/main/contracts/FirelightVault.sol

  • Impacts:

    • Manipulation of governance voting result deviating from voted outcome and resulting in a direct change from intended effect of original results

    • Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)

Description

Brief/Intro

The FirelightVault implementation contract lacks protection against direct initialization, allowing any attacker to call the initialize() function on the implementation contract (not the proxy) and gain DEFAULT_ADMIN_ROLE privileges on it. While this does not directly compromise the proxy's storage or user funds due to the separation of concerns in the proxy pattern, it creates a critical security risk where an attacker controls the implementation contract's state. This could lead to confusion, potential denial-of-service scenarios, and manipulation if the implementation contract has any self-referential logic or if future upgrades rely on implementation state.

Vulnerability Details

The FirelightVault contract is deployed using OpenZeppelin's upgradeable proxy pattern (UUPS). In this pattern, there are two separate contracts:

  1. Proxy contract - holds all storage and delegates calls to the implementation

  2. Implementation contract - contains the logic but should never be initialized

The vulnerability exists because the implementation contract does not have a constructor that calls _disableInitializers() to prevent initialization. This is a critical security requirement for all OpenZeppelin upgradeable contracts.

Vulnerable Code Location: contracts/FirelightVault.sol lines 146-196

The Missing Protection:

There is no constructor like this:

Attack Flow:

  1. Attacker obtains the implementation contract address from the proxy's EIP-1967 storage slot (0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc)

  2. Attacker calls initialize() directly on the implementation contract with their own parameters

  3. The initializer modifier allows this because the implementation has never been initialized

  4. Attacker's address receives DEFAULT_ADMIN_ROLE on the implementation contract

  5. The implementation contract's state is now controlled by the attacker

Impact Details

Immediate Impact:

  • Attacker gains DEFAULT_ADMIN_ROLE on the implementation contract

  • Attacker can grant themselves all other roles (RESCUER_ROLE, BLOCKLIST_ROLE, PAUSE_ROLE, etc.) on the implementation

  • Implementation contract state variables can be manipulated by the attacker

Potential Consequences:

  1. Confusion and Trust Issues: Users and monitoring systems may detect that the implementation contract has been initialized with unexpected values, causing confusion and loss of trust in the protocol.

  2. Upgrade Manipulation Risk: If future upgrade logic or validation checks reference the implementation contract's state (which should never happen but could be introduced accidentally), the attacker could manipulate upgrade processes.

  3. Denial of Service: The attacker could potentially call functions on the implementation that cause it to enter an invalid state, which might affect future upgrades or cause revert conditions.

  4. Governance Confusion: If any governance mechanisms reference the implementation contract (even indirectly), the attacker's control over admin roles could create voting or decision-making issues.

Severity Justification: While the proxy's storage remains safe and user funds are not directly at risk, this vulnerability violates a fundamental security principle of upgradeable contracts. The implementation should be immutable and uninitialized. This is classified as griefing because the attacker gains no direct financial benefit but can cause significant damage to the protocol's integrity and operations.

References

  • OpenZeppelin Upgradeable Contracts Documentation: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract

  • OpenZeppelin Security Advisory on Implementation Initialization: https://docs.openzeppelin.com/contracts/4.x/api/proxy#Initializable

  • Similar vulnerability in Meta Pool (mpETH) - 2025

  • EIP-1967 Proxy Storage Slots: https://eips.ethereum.org/EIPS/eip-1967


Proof of Concept

Proof of Concept

Test File: test/pocs/a_implementation_initializer.js

Prerequisites:

Step-by-Step Reproduction:

Step 1: Deploy the vault using the normal proxy deployment (this happens in the test setup)

Step 2: Extract the implementation contract address from the proxy

Step 3: Attach to the implementation contract directly (bypassing the proxy)

Step 4: Prepare malicious initialization parameters

Step 5: Call initialize on the implementation contract

Step 6: Verify attacker gained admin role on implementation

Run the Complete PoC:

Expected Output:

Concrete Evidence:

  • Implementation Address: 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9

  • Attack Transaction: 0x6e3f0697aa1580947f7b49fff0f5c91f735469e9c0c5788c1c30e34f55f7c7c0

  • Attacker Role Verification: hasRole(DEFAULT_ADMIN_ROLE, attacker) = true

  • Implementation State Modified: contractVersion = 1, depositLimit = 50000000000

Remediation:

Add this constructor to FirelightVault.sol:

This will permanently disable initialization on the implementation contract while still allowing the proxy to be initialized normally.

Was this helpful?