HI Cyber Con CTF 2026. Blockchain.
Quiet Upgrade
Two contracts shared storage slot 0. The lab took the admin seat and emptied a 5,000,000 token treasury in 2 minutes.
Published 2 November 2026. Challenge by Hidden Investigations.
The brief
The upgrade path was added in a later release and was never checked against the proxy's existing storage layout. Each team gets a private chain. You're given an RPC URL, a funded private key, and the address of the Setup contract. Win condition: the settlement node's treasury ends holding less than 1% of its starting reserve. Submit through the checker once Setup.isSolved() returns true.
- quiet-upgrade.zip, {size}
In short
A naive upgradeable proxy kept its admin in storage slot 0, and the logic contract behind it let anyone write its own slot 0. The lab became admin with one call, upgraded the proxy to its own contract and released the whole 5,000,000 STL reserve.
Admin and code in slots 0 and 1
The settlement node runs behind an upgradeable proxy. The proxy keeps its admin in storage slot 0 and its implementation in slot 1, and hands every other call to the logic contract through delegatecall.
Two variables, one slot
Through delegatecall the logic contract writes the proxy's own storage. Its slot 0 is branchOperator, and registerBranch() lets any caller set it once. Setting branchOperator overwrites the admin.
One call to become admin
The lab called registerBranch() through the proxy with its own address. No owner check stood in the way, and admin() now returned the lab's account instead of the Ops contract.
An upgrade check that binds nothing
The upgrade only asks the new code for proxiableSlot(), a hash of a fixed string and the proxy address. The lab computed it in advance as 0x6946e11c, built a contract that returns it and upgraded the proxy to it.
The treasury trusts the proxy
The treasury pays any call that comes from the proxy, and the lab's drain() ran inside the proxy. It released all 5,000,000 STL, far below the 1 percent win line, and isSolved() returned true.
The insight
The proxy's admin and the logic contract's branchOperator shared slot 0, so an open function meant for branches handed anyone the right to upgrade.
The flag
hiCTF{sl0t0_c0llision_then_a_qu1et_upgrade_0cc968a1b2}