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).
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.
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.
Initialize atomically by passing the encoded initialize calldata to the proxy constructor.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.