Thunder Loan

AI First Flight #7
Beginner FriendlyFoundryDeFiOracle
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

`initialize()` is an unprotected separate transaction (proxy deployed with empty init data), allowing anyone to front-run initialization and seize ownership

Description

Normal behavior: the proxy owner and pool factory should be set atomically at deployment.

Specific issue: initialize() is external initializer with no restriction on the caller, and the deploy script constructs ERC1967Proxy(impl, "") with EMPTY init data, so initialization is a separate, unprotected transaction. Between deploy and the owner's initialize call, anyone can call initialize(attackerPoolFactory) on the proxy: __Ownable_init() sets _owner = msg.sender (the attacker) and the attacker sets a malicious pool factory (which controls the price oracle).

function initialize(address tswapAddress) external initializer { // @> anyone can call first
__Ownable_init(); // @> sets owner = msg.sender
...
__Oracle_init(tswapAddress); // @> attacker sets malicious factory
}
// DeployThunderLoan.s.sol: new ERC1967Proxy(address(impl), ""); // @> not initialized atomically

Risk

Likelihood:

  • Requires winning the race between deployment and the legitimate initialize call (mempool-observable).

Impact:

  • Attacker becomes owner (controls upgrades and allowed tokens) and sets a malicious price oracle. Full protocol compromise if the race is won.

Proof of Concept

Read script/DeployThunderLoan.s.sol: the proxy is created with "" init data, so initialize is a distinct unprotected call on the deployed proxy. Any address that calls thunderLoan.initialize(attackerFactory) before the deployer does becomes owner (__Ownable_init sets _owner = msg.sender); the second call reverts with InvalidInitialization, locking the attacker in.

Recommended Mitigation

Initialize atomically by passing the encoded initialize calldata to the proxy constructor.

- ERC1967Proxy proxy = new ERC1967Proxy(address(impl), "");
+ bytes memory data = abi.encodeCall(ThunderLoan.initialize, (tswapPoolFactory));
+ ERC1967Proxy proxy = new ERC1967Proxy(address(impl), data);
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!