Hawk High

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

Unprotected Initialize Function in Upgradeable Contract can be frontrun

Summary

The contract implements an initialize function that lacks proper access control mechanisms. While the function uses the initializer modifier to prevent multiple initializations, it is declared as public without any additional access control, making it vulnerable to front-running attacks during deployment.

Vulnerability Details


@> function initialize(address _principal, uint256 _schoolFees, address _usdcAddress) public initializer {
if (_principal == address(0)) {
revert HH__ZeroAddress();
}
if (_schoolFees == 0) {
revert HH__ZeroValue();
}
if (_usdcAddress == address(0)) {
revert HH__ZeroAddress();
}
principal = _principal;
schoolFees = _schoolFees;
usdc = IERC20(_usdcAddress);
__UUPSUpgradeable_init();
}

Impact

An attacker could monitor the mempool for the contract deployment transaction and front-run the legitimate initialization by calling the initialize function before the intended owner. This would allow the attacker to:

  1. Set themselves as the principal with administrative privileges

  2. Control critical parameters like schoolFees

  3. Specify the usdcAddress to potentially point to a malicious token contract

Since this is a UUPS upgradeable contract (as evidenced by the __UUPSUpgradeable_init() call), the consequences are particularly severe. The attacker would gain permanent control over the contract, including the ability to upgrade it to malicious implementations.

Tools Used

Manual Review

Recommendations

Implement proper access control for the initialize function by using OpenZeppelin's OwnableUpgradeable pattern:

// Import OwnableUpgradeable
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
// Update contract inheritance
contract LevelOne is Initializable, UUPSUpgradeable, OwnableUpgradeable {
// ...existing code...
function initialize(address _principal, uint256 _schoolFees, address _usdcAddress)
external // Changed from public to external for gas optimization
initializer
{
// Input validations
if (_principal == address(0)) {
revert HH__ZeroAddress();
}
if (_schoolFees == 0) {
revert HH__ZeroValue();
}
if (_usdcAddress == address(0)) {
revert HH__ZeroAddress();
}
// Initialize inherited contracts
__Ownable_init();
__UUPSUpgradeable_init();
// Set contract state
principal = _principal;
schoolFees = _schoolFees;
usdc = IERC20(_usdcAddress);
// Optional: Transfer ownership to principal if different from deployer
if (_principal != msg.sender) {
_transferOwnership(_principal);
}
}
// Update authorization for upgrades
function _authorizeUpgrade(address newImplementation)
internal
override
onlyOwner // Use Ownable's modifier
{}
}

This implementation follows industry best practices and provides several security benefits:

  • Proper initialization sequence for all inherited contracts

  • Ownership control through OpenZeppelin's well-tested patterns

  • Protection against front-running through the initializer pattern

  • External visibility for better gas optimization

Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 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()`

yeahchibyke Lead Judge 3 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.