samczsun Profile picture
research @paradigm, founder @_SEAL_Org. art by @Keiseeaaa/@vincywp emergency help: https://t.co/uDNDg1xCgR https://t.co/1IDOUbRX6v

Aug 1, 2022, 12 tweets

1/ Nomad just got drained for over $150M in one of the most chaotic hacks that Web3 has ever seen. How exactly did this happen, and what was the root cause? Allow me to take you behind the scenes 👇

2/ It all started when @officer_cia shared @spreekaway's tweet in the ETHSecurity Telegram channel. Although I had no idea what was going on at the time, just the sheer volume of assets leaving the bridge was clearly a bad sign

3/ My first thought was that there was some misconfiguration for the token's decimals. After all, it seemed as though the bridge was running a "send 0.01 WBTC, get 100 WBTC back" promotion

4/ However, after some painful manual digging on the Moonbeam network, I confirmed that while the Moonbeam transaction did bridge out 0.01 WBTC, somehow the Ethereum transaction bridged in 100 WBTC

moonscan.io/tx/0xcca9299c7…
etherscan.io/tx/0xa5fe9d044…

5/ Furthermore, the transaction to bridge in the WBTC didn't actually prove anything. It simply called `process` directly. Suffice to say, being able to process a message without proving it first is extremely Not Good

6/ At this point, there were two possibilities. Either the proof had been submitted separately in an earlier block, or there was something extremely wrong with the Replica contract. However, there was absolutely no indication that anything had been proven recently

7/ This left only one possibility - there was a fatal flaw within the Replica contract. But how? A quick look suggests that the message submitted must belong to an acceptable root. Otherwise, the check on line 185 would fail

8/ Fortunately, there's an easy way to sanity check this assumption. I knew that the root of a message which had not been proven would be 0x00, because messages[_messageHash] would be uninitialized. All I had to do was check whether the contract would accept that as a root

9/ Oops

10/ It turns out that during a routine upgrade, the Nomad team initialized the trusted root to be 0x00. To be clear, using zero values as initialization values is a common practice. Unfortunately, in this case it had a tiny side effect of auto-proving every message

11/ This is why the hack was so chaotic - you didn't need to know about Solidity or Merkle Trees or anything like that. All you had to do was find a transaction that worked, find/replace the other person's address with yours, and then re-broadcast it

12/ tl;dr a routine upgrade marked the zero hash as a valid root, which had the effect of allowing messages to be spoofed on Nomad. Attackers abused this to copy/paste transactions and quickly drained the bridge in a frenzied free-for-all

Share this Scrolly Tale with your friends.

A Scrolly Tale is a new way to read Twitter threads with a more visually immersive experience.
Discover more beautiful Scrolly Tales like this.

Keep scrolling