Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: medium
Valid

Missing `_disableInitializers()` exposes implementation contract to unauthorised initialisation

Summary

The implementation contract does not follow best practices for upgradeable contracts: the initialize function is declared public, and no constructor disables initialisers on the implementation contract. As a result, anyone can call initialize directly on the implementation contract.

According to OpenZeppelin’s recommendations, the LevelOne contract should include a constructor with _disableInitializers() to prevent unauthorised initialisation of the implementation contract.

Vulnerability Details

A key risk in upgradeable proxy patterns is that the implementation contract is deployed independently and accessible on-chain. While the initialize function is intended to be called via the proxy, it is also publicly accessible on the implementation contract itself.

Because initialize is public, any address can call it directly on the implementation contract. If initialisation has not yet occurred on the implementation contract, the caller can assign themselves as principal. This creates a scenario where there are effectively two owners:

  1. The principal stored in the proxy contract’s storage

  2. The principal stored in the implementation contract’s storage

Functions protected by onlyPrincipal modifiers may check ownership against the contract’s own storage, resulting in unexpected access control behaviour if called on the implementation contract directly.

While this issue does not affect the proxy storage or proxy-based interactions, it leaves the implementation contract unnecessarily exposed and potentially abusable in future deployments, integrations, or tooling relying on the implementation contract.

OpenZeppelin recommends including the following constructor in upgradeable contracts to disable initializers on the implementation contract:

constructor() {
_disableInitializers();
}

This prevents any future call to initialize() or any reinitializer() function on the implementation contract itself.

Impact

Impact Classification: Medium - reduces risk of accidental or malicious use of implementation contract, which could result in unintended access control the implementation contract.

Likelihood Classification: Low - requires someone to intentionally call implementation contract

Tools Used

  • Manual code review of the initialize function

  • Reference to OpenZeppelin documentation and upgradeable contract best practices

Recommendations

  • Add a constructor with the _disableInitializers() function to the implementation contract.

constructor() {
_disableInitializers();
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 27 days ago
Submission Judgement Published
Validated
Assigned finding tags:

contract can be re-initialized

The system can be re-initialized by an attacker and its integrity tampered with due to lack of `disableInitializer()`

Support

FAQs

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