The LevelOne::initialize function lacks proper access control, allowing any user to call it and potentially take ownership of the contract, which could lead to its compromise or destruction.
The LevelOne::initialize function is publicly accessible and does not restrict who can invoke it. As a result, any external actor can initialize the contract at any time.
This vulnerability allows a malicious actor to initialize the contract before its intended owner, potentially assigning themselves as the principal. Once in control, they can point the proxy to a malicious implementation, compromising the system’s integrity and functionality.
The vulnerability can be exploited through the following steps:
Deploy the LevelOne contract using a script (e.g., DeployLevelOne.s.sol) that does not perform initialization.
An unauthorized actor, such as a disgruntled teacher alice, notices the contract is uninitialized.
alice calls the initialize function and assigns herself as the principal.
With the principal role, alice executes upgradeToAndCall, directing the proxy to a malicious EvilImplementation contract that includes a dummy doNothing function or other undesired logic.
The proxy is now pointing to malicious code, and control of the contract has effectively been seized.
In the DeployLevelOne.s.sol deployment script, the LevelOne contract is deployed without calling the initialize function, leaving it vulnerable to unauthorized initialization:
2.At the end of the LevelOneAndGraduate.t.sol file, a malicious implementation named EvilImplementation is defined. This contract contains a doNothing function to simulate arbitrary, undesired logic and mimics a valid UUPS proxy target by implementing proxiableUUID.
3.Then, inside the LevelOneAndGraduateTest contract, a test case demonstrates how a supposedly unauthorized user alice can initialize the proxy and redirect it to the malicious implementation:
Once this test passes, it proves that the contract was initialized by an unauthorized actor alice and then redirected to the malicious EvilImplementation. As a result, subsequent calls to functions defined in the original LevelOne contract (like getPrincipal) revert, since the proxy now points to a contract lacking those definitions.
Slither was used to identify this vulnerability.
To prevent unauthorized initialization and secure the upgradeable proxy pattern, it is strongly recommended to enforce access control on the initialize function of the LevelOne contract.
Import OwnableUpgradeable from OpenZeppelin to enable access control:
2.Call __Ownable_init() within the initialize function, before initializing UUPS:
Note: __Ownable_init() sets the caller msg.sender as the initial owner. If needed, you can pass a custom address instead of msg.sender.
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.