The Stratax contract is an upgradeable implementation designed to be used behind a Beacon Proxy pattern. It inherits from OpenZeppelin's Initializable and uses an initialize() function with the initializer modifier to set up state, including setting owner = msg.sender. However, the contract does not define a constructor that calls _disableInitializers(). This means the implementation contract itself (as opposed to the proxy) remains uninitialised after deployment. Because proxy initialisation only affects proxy storage, the implementation's _initialized flag remains at zero, leaving initialize() callable by anyone on the bare implementation address.
When a caller invokes initialize() on the implementation, the initializer modifier passes because _initialized == 0, and the caller becomes the owner on the implementation's storage. The attacker then has access to all onlyOwner functions on the implementation, including recoverTokens(), setStrataxOracle(), setFlashLoanFee(), and transferOwnership().
The deployment script deploys the implementation without any initialiser lock:
OpenZeppelin's official documentation for Initializable explicitly warns about this scenario, stating that an uninitialised contract can be taken over by an attacker and recommending that _disableInitializers() be invoked in the constructor. Real-world precedent exists: the Wormhole bridge exploit (February 2022) involved an uninitialised implementation contract, and OpenZeppelin disclosed CVE-2021-41264, a UUPS vulnerability arising from the same class of issue.
This issue has a medium impact as the primary damage is limited to tokens accidentally sent to the implementation address and control over the implementation's state. Proxy users' funds and state are not directly at risk because the Beacon Proxy pattern does not allow the attacker to upgrade or destroy the proxy through the implementation.
This issue has a high likelihood as the attack requires no special privileges, no preconditions, and is trivially executable. The implementation address is publicly visible onchain, and automated bots actively scan for this pattern.
Add a constructor to the Stratax contract that calls _disableInitializers() to permanently lock the implementation contract against direct initialisation. This sets _initialized to type(uint64).max on the implementation's own storage during deployment, preventing any future calls to initialize() or reinitializer() on the implementation directly. The proxy's storage is unaffected because the constructor runs in the implementation's storage context, not the proxy's.
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.
The contest is complete and the rewards are being distributed.