Hyper's OP Stack bridge exploit analysis
On December 12th, 2023, Hypr's OP Stack Bridge experienced an exploit. The details of the attack transaction on Etherscan can be found at:
https://etherscan.io/tx/0x51ce3d9cfc85c1f6a532b908bb2debb16c7569eb8b76effe614016aac6635f65.
Introduction
Before diving into the details of the attack, having some basic understanding about the target is nescessary. Hypr Network is an OP Stack chain which is a Layer2 rollup network based on Optimism. In the long term, multiple OP Stack chains, including the Optimism mainnet, will form to a new big network called Superchain. As the OP Stack source code is open-sourced, an increasing number of Layer2 networks are built based on it. The target of this attack is the L1StandardBridge
contract on L1 chain (Ethereum) which allows token bridging between L1 and L2 (Hypr Network).
Exploit analysis
When analyzing the attack transaction, in the attacker_contract, we can see a call to the L1ChugSplashProxy.initialize()
which set the messenger
address point to the attacker_contract
address. How can this function be called from the attacker contract? In a typical scenario, this function should only be invoked once during the contract initialization process.
To further analyze this, let’s examine the implementation of the L1StandardBridge.initialize()
function. Do you notice something strange here? The clearLegacySlot
modifier here will reset the value of the storage slot 0. This action, in turn, resets the value of storage slot 0 of the Initializable
contract (to verify this, we can inspect the inheritance chain of the L1StandardBridge
contract). If we read the comments carefully, we’ll know that the current version of this code is not ready for production. So, that’s the reason why this function can be called multiple times.
In the subsequent step, the attacker contract invokes the finalizeERC20Withdrawal()
function, resulting in the transfer of a substantial amount of HYPR
tokens (2,570,000 HYPR
).
Checking out the implementation of the finalizeERC20Withdrawal
function, we can see that this function is guarded by the onlyOtherBridge
modifier (in the finalizeBridgeERC20
function).
By setting the messenger
address to the attacker contract, the attacker can easily bypass this check and successfully withdraw the HYPR
tokens deposited in this contract.
The total loss is approximately 2.57 million HYPR
, which was bridged by two users at the time of the attacks.
Lessons learned
When building your own project based on any existing open-sourced project, like OP Stack in this case, it is crucial to ensure that your project is forked from a correct release version that is production-ready. Moreover, after forking the original project, it is essential to monitor the repository for any important security updates. Having security audit is always recommended in these cases, especially for projects that can hold large assets, such as bridges or AMM pools.