Stratax Contracts

First Flight #57
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: high
Likelihood: high

unchecked initialization

Author Revealed upon completion

Root + Impact

Description

  • The initialize() function configures critical protocol dependencies such as the Aave pool, data provider, 1inch router, USDC token, and oracle. It also assigns contract ownership and sets the flash loan fee. The initializer modifier ensures it can only be executed once in an upgradeable deployment.

  • The function is declared as external initializer without access control and without validating input addresses. This allows any external account to call initialize() before the intended deployer, permanently take ownership, and inject malicious protocol addresses.

function initialize(
address _aavePool,
address _aaveDataProvider,
address _oneInchRouter,
address _usdc,
address _strataxOracle
) external initializer {
aavePool = IPool(_aavePool);
aaveDataProvider = IProtocolDataProvider(_aaveDataProvider);
oneInchRouter = IAggregationRouter(_oneInchRouter);
USDC = _usdc;
strataxOracle = _strataxOracle;
owner = msg.sender;
flashLoanFeeBps = 9;
}

Risk

Likelihood: HIGH

  • During proxy deployment, the contract remains uninitialized until initialize() is executed, creating a publicly accessible execution window.

  • Attackers and automated bots actively monitor newly deployed upgradeable contracts and execute unprotected initializer functions immediately.

Impact: CRITICAL

  • Permanent ownership takeover by an attacker.

  • Injection of malicious external protocol addresses leading to fund theft, oracle manipulation, or full protocol compromise.

Proof of Concept

An attacker can front-run the legitimate deployer and initialize the contract first.

// Attacker calls initialize before the deployer
stratax.initialize(
attackerControlledAavePool,
attackerControlledDataProvider,
attackerRouter,
attackerFakeUSDC,
attackerOracle
);
// Attacker is now the owner
assert(stratax.owner() == attacker);

Explanation

  1. Proxy contract is deployed.

  2. initialize() has not yet been executed.

  3. The attacker observes the deployment in the mempool.

  4. The attacker calls initialize() first.

  5. Ownership is permanently assigned to the attacker and malicious protocol addresses are stored.

Since initializer prevents re-initialization, the legitimate deployer cannot recover control.

Recommended Mitigation

The initialize() function should be restricted to a trusted deployer and include validation for all critical addresses. Additionally, when using OpenZeppelin upgradeable contracts, __Ownable_init() should be used instead of manually assigning ownership.

function initialize(
address _aavePool,
address _aaveDataProvider,
address _oneInchRouter,
address _usdc,
address _strataxOracle
- ) external initializer {
+ ) external initializer {
+ __Ownable_init();
+ require(_aavePool != address(0), "Invalid Aave pool");
+ require(_aaveDataProvider != address(0), "Invalid data provider");
+ require(_oneInchRouter != address(0), "Invalid router");
+ require(_usdc != address(0), "Invalid USDC");
+ require(_strataxOracle != address(0), "Invalid oracle");
aavePool = IPool(_aavePool);
aaveDataProvider = IProtocolDataProvider(_aaveDataProvider);
oneInchRouter = IAggregationRouter(_oneInchRouter);
USDC = _usdc;
strataxOracle = _strataxOracle;
- owner = msg.sender;
flashLoanFeeBps = 9;
}

Mitigation Explanation

  • Restricting initialization prevents unauthorized ownership takeover.

  • Zero-address validation prevents accidental or malicious misconfiguration.

  • Using __Ownable_init() ensures proper upgradeable ownership initialization.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!