Description: The logic of LevelOne::initialize
function is marked public initializer, but there is no call to _disableInitializers()
in the implementation's constructor. That means anyone can call initialize on the logic contract and become principal, set arbitrary schoolFees or usdc, and then drain or destroy funds.
Impact: This runs initialize()
in the implementation contract's own storage, sets '_initialized = 1' in the logic contract, and potentially sets 'owner = attacker'. An attacker can hijack the contract by re-initializing the logic implementation, minting themselves ownership and privileges, and either steal USDC or upgrade to malicious code. Also, the attacker can interfere with reinitializer(x) logic.
Proof of Concept: Include the following test in the LevelOneAndGraduateTest.t.sol
file:
Even if the proxy was correctly initialized first, the logic contract is now initialized with its own state (could confuse tools like Etherscan, OpenZeppelin Defender) and has its own owner (someone may accidentally interact with the wrong address).
Recommended Mitigation: In the implementation contract's constructor, call _disableInitializers()
to ensure initialize cannot be called on the logic contract.
This prevents anyone from initializing the logic contract itself and ensures only the proxy can be initialized.
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.