The initialize function in the LevelOne contract lacks proper access control, allowing any external attacker to call it before the legitimate deployer. This vulnerability enables an attacker to set themselves as the principal, giving them complete control over the contract, including teacher management, student expulsion, and funds distribution.
The initialize function in the LevelOne contract is implemented with the initializer modifier from OpenZeppelin's Initializable contract, but does not include any access control mechanism to restrict who can call it:
The vulnerability arises from the function being public without any access control or validation that the caller is the legitimate deployer. In upgradeable contract patterns, the initialization function replaces the constructor, but unlike constructors which automatically execute during deployment and are inaccessible afterward, initializers require explicit protection.
This issue creates a critical front-running opportunity: an attacker monitoring the mempool can detect the deployment transaction and front-run it with their own initialization call, setting themselves as principal before the legitimate deployer can initialize the contract.
As the principal, the attacker would have complete control over the contract, including:
Adding/removing teachers
Expelling students
Setting cutoff scores
Starting sessions
Triggering upgrades and distributing funds
The impact of this vulnerability is severe:
Complete contract takeover by an unauthorized party
Potential theft of all collected school fees (via the graduateAndUpgrade function)
Ability to control who can teach or enroll
Complete subversion of the platform's intended academic governance
While examining the project repository, we found that the actual deployment script (DeployLevelOne.s.sol) does implement proper atomic deployment, where contract creation and initialization happen in the same transaction, which mitigates this vulnerability in practice. However, the contract itself still lacks built-in protection, which means any alternative deployment method that doesn't use this script would be vulnerable. This represents a moderate security concern rather than a critical failure since proper deployment procedures appear to be in place.
Manual code review
Implement proper access control for the initialization function. Here are two effective approaches:
A factory contract pattern is the most secure approach as it ensures that initialization happens in the same transaction as deployment, making front-running impossible:
With this approach, the entire deployment and initialization happens atomically in a single transaction, completely eliminating the front-running risk.
If a factory pattern cannot be used, add explicit access control to the initializer:
This ensures that only the address that deployed the contract can initialize it, preventing unauthorized initialization.
Consider using OpenZeppelin's TransparentUpgradeableProxy instead of the current UUPS pattern. The transparent proxy pattern includes built-in access control for admin functions and can provide additional protection against similar vulnerabilities.
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.