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.