Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: medium
Valid

First-to-Initialize Vulnerability in Proxy Deployment

Summary

The contract's initialization process is vulnerable to a front-running attack where anyone can initialize the proxy contract and take control of the system by becoming the principal.

Vulnerability Details

=> The initialize function is public and only protected by OpenZeppelin's initializer modifier

=> The initializer modifier only prevents re-initialization, not unauthorized initialization

Real-World Attack Scenario:

  1. Deployer submits transaction to deploy proxy and implementation

  2. Before the initialization transaction is mined

  3. Attacker front-runs with their own initialization transaction

  4. Attacker becomes principal and can:

  • Control teacher appointments

  • Set arbitrary school fees

  • Control student expulsions

  • Control the upgrade process

Impact

  • Anyone can initialize the proxy contract if they are the first to call initialize

  • An attacker can front-run the deployment transaction and take control of the contract

  • This would allow the attacker to become the principal and control all school operations

PoC

import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
function test_anyone_can_initialize_fresh_deployment() public {
// Deploy just the implementation contract without initializing
LevelOne implementation = new LevelOne();
ERC1967Proxy proxy = new ERC1967Proxy(address(implementation), "");
LevelOne levelOneProxy = LevelOne(address(proxy));
// Random address tries to initialize before anyone else
address attacker = makeAddr("attacker");
MockUSDC newUsdc = new MockUSDC();
vm.startPrank(attacker);
// This succeeds! Attacker becomes principal
levelOneProxy.initialize(
attacker, // attacker becomes principal
100e18, // arbitrary school fees
address(newUsdc)
);
vm.stopPrank();
// Attacker now controls the contract
assertEq(levelOneProxy.getPrincipal(), attacker);
}

Tools Used

manual review

Recommendations

instead of the current implementation

proxy = new ERC1967Proxy(address(levelOneImplementation), "");

use atomic deployment

// Encode the initialization call
bytes memory initData = abi.encodeWithSelector(
LevelOne.initialize.selector,
principal,
schoolFees,
address(usdc)
);
// Deploy proxy with initialization data
proxy = new ERC1967Proxy(
address(levelOneImplementation),
initData
);
Updates

Lead Judging Commences

yeahchibyke Lead Judge
2 months ago
yeahchibyke Lead Judge 2 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

mishoko Submitter
2 months ago
yeahchibyke Lead Judge 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

contract can be re-initialized

The system can be re-initialized by an attacker and its integrity tampered with due to lack of `disableInitializer()`

Support

FAQs

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