Nomad Bridge Exploit
A detailed insight into Crypto’s fourth largest protocol exploit
The Nomad bridge was recently exploited for over $190m. While the complete story of the hack keeps unfolding gradually, we would love to throw light on some details about the hack.
At first glance, it seemed that the token's decimals had been misconfigured.
Thanks for reading my Newsletter! Subscribe for free to receive new posts and support my work.
This meant that either the proof was submitted separately in an earlier block, or something was seriously wrong with the Replica contract.
However, there was no indication that anything of such had recently been proven.
This left only one possibility: the Replica contract contained a fatal flaw.
But how exactly?
A closer look indicates that the message submitted must be from an acceptable root. Otherwise, line 185's check would fail.
Fortunately, there is a simple way to validate this assumption. We knew that the root of an unproven message would be 0x00 because messages[_messageHash] would be uninitialized. All we had to do, was to check if the contract would accept it as a root.
It turned out that the Nomad team set the trusted root to 0x00 during a routine upgrade. They also changed how messages were checked to be proven.
To be clear, it is common practice to use zero values as initialization values. Unfortunately, in this case, it had the unintended consequence of automatically proving every message.
Looking at the transactions that have interacted with the contract, we can see that all of these exploits have one thing in common: they all use the Nomad ERC20 Bridge Contract's 'process()' function.
How does the 'process()' function work?
The 'process()' function, located at 0xB92336759618F55bd0F8313bd843604592E27bd8 in the current implementation contract, verifies that messages contain an acceptable Merkle root. It verifies that the message's domain is correct (e.g., a transaction signed for evmos is intended for evmos) - It also verifies that the prover has proven the message and instructs the handler to carry out the message's request (i.e. bridge tokens).
Unfortunately, attempting to replay the same contract call will result in the execution being reverted.
Because you should not be able to withdraw the same assets more than once.
But during the hack, the bridge appeared to allow users to withdraw an arbitrary amount that does not necessarily correspond to the amount they deposited into nomad on the other chain.
This means that the processor contract did not validate the received message payload.
To further understand this, it is important to note that Nomad works in 2 steps:
The user sends a token from X chain
User processes token withdrawal on Y chain
But during the exploit, in step 2, the bridge allowed the user to pass in an arbitrary amount.
Example: A user transfers 0.2 WETH from Moonbeam to Ethereum. In event 2, a message is generated and sent to the Ethereum processor contract.
This is the erroneous code.
To make dealing with raw bytes easier, Nomad employs a bytes processing library. When receiving a payload, they do not validate the message body and instead accept any input from the user.
However, some generalized MEV front-running bots were able to run some simulations on address replacements in calldata, replay the previous attacks and withdraw large amounts of WETH/WBTC.
When Nomad noticed this, they paused the relayer and attempted to censor all bridging transactions via the watcher; however, this wasn’t much of assistance, as the exploit was on the contract side rather than the infra side.
Their initial upgrade had already recognized the zero hash as a valid root, thereby allowing Nomad messages to be spoofed. Attackers took advantage of this by copying and pasting transactions, quickly draining the bridge in a frenzied free-for-all.
You didn't have to know anything about Solidity, Merkle Trees, or anything else. All you had to do was to find the original hacker's transaction calldata, copy and replace the original address with a personal one, and then re-broadcast it.
As simple as CTRL-C and CTRL-V.
According to defillama, Nomad had a tvl of $198 million prior to the attack.
The contract currently has only $97k remaining. All these happened in a matter of hours!
Unlikely saviors - Recovery of Nomad Bridge Funds
Following the initial attack, hundreds of separate accounts figured out the trick and replicated it. It was a potpourri of hackers; blackhats and whitehats.
While the blackhats have kept their loot, a couple of the whitehats that managed to grab some of the bridge funds, have publicly come forward and offered to return.
The team has also sent out a note to white hat hackers and ethical researchers, who have been storing some of the ETH/ERC-20 tokens.
Please send the funds to the Ethereum wallet address :
Truth be told, this all seems to be very bad unfortunate timing. The Nomad team recently announced significant backing after raising a $22 million round several months ago. And now, a hack? It's all so sad.
The Nomad vulnerability affected chains like Moonbeam, Evmos, and Milkomeda. These are chains that use nomad assets as their canonical assets. It now appears that Vitalik's views on cross-chain vs. multichain are more prescient than ever, as there isn't a single bridge left standing that hasn't been hacked.
As crypto users, we need to be more careful in using DeFi products. There is little to no insurance backing in the crypto space. If any of your protocols is hacked, the money is almost certainly gone.
It's a cold world out there.
Thanks for reading Kyrian Alex's Newsletter! Subscribe for free to receive new posts and support my work.