Medium
Both LevelOne and LevelTwo contracts lack the critical _disableInitializers()
protection mechanism in their constructors. This omission exposes the implementation contracts to potential initialization attacks where an attacker could:
Directly initialize the implementation contract (not the proxy)
Potentially interfere with the upgrade process
Manipulate the implementation contract's state in ways that could impact the proxy's behavior
In extreme cases, execute a destructive operation like selfdestruct
on the implementation
This vulnerability affects both implementation contracts in the UUPS upgrade chain and could lead to system disruption or fund loss.
Additionally, the initialize()
function accepts an arbitrary USDC address parameter with minimal validation (only checking for non-zero address). An attacker who initializes the implementation could specify a malicious ERC20 contract address instead of the real USDC. This would create a denial-of-service condition for the entire school system:
Students attempting to enroll with real USDC would have their transactions revert, as their approvals would be for the real USDC contract while the system is trying to use the malicious one
The school system would appear properly deployed but would be completely unusable
When the system is upgraded, teachers and the principal would receive worthless tokens as payment instead of real USDC
The entire economic model of the school would be compromised
Upgradeable contracts using the UUPS pattern have two components:
A proxy contract that stores state
An implementation contract that contains logic
Without proper protection, an attacker can call initialization functions directly on the implementation contract rather than through the proxy. In the Hawk High system:
LevelOne.sol
has the initialize()
function that lacks implementation-level protection
LevelTwo.sol
has the graduate()
function with reinitializer(2)
that also lacks protection
OpenZeppelin's best practice is to include a constructor with _disableInitializers()
in all upgradeable implementation contracts to prevent direct initialization. Both contracts are missing this critical security mechanism:
The lack of proper initialization protection combined with insufficient token validation creates a particularly concerning scenario:
An attacker could:
Deploy their own malicious ERC20 token that implements the IERC20 interface
Directly initialize the implementation contract with their malicious token address
The contract would accept this token as it passes the minimal validation checks
All genuine USDC operations attempted through the contract would fail
The contract would be unusable for legitimate participants
Since the school system handles funds (USDC), this vulnerability could potentially lead to financial loss or complete system failure if an attacker disrupts the enrollment process or manipulates the implementation contract's state.
An attacker could execute:
Deploy a malicious ERC20 token:
Directly initialize the implementation:
Now the implementation has been initialized with a malicious token. Any upgrade processes that reference the implementation will be affected, and the malicious token will prevent normal operation.
Add a constructor with _disableInitializers()
to both LevelOne and LevelTwo contracts:
Additionally, implement stronger validation for the USDC address in the initialize function:
The special comment /// @custom:oz-upgrades-unsafe-allow constructor
is required to tell the OpenZeppelin Upgrades plugin that this constructor is intentionally included and safe.
The system can be re-initialized by an attacker and its integrity tampered with due to lack of `disableInitializer()`
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.