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:
Proxy contract - holds all storage and delegates calls to the implementation
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:
Attacker obtains the implementation contract address from the proxy's EIP-1967 storage slot (
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc)Attacker calls
initialize()directly on the implementation contract with their own parametersThe
initializermodifier allows this because the implementation has never been initializedAttacker's address receives
DEFAULT_ADMIN_ROLEon the implementation contractThe implementation contract's state is now controlled by the attacker
Impact Details
Immediate Impact:
Attacker gains
DEFAULT_ADMIN_ROLEon the implementation contractAttacker 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:
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.
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.
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.
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:
0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9Attack Transaction:
0x6e3f0697aa1580947f7b49fff0f5c91f735469e9c0c5788c1c30e34f55f7c7c0Attacker Role Verification:
hasRole(DEFAULT_ADMIN_ROLE, attacker) = trueImplementation 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?