☠ Case Studies
← All Case Studies

Nomad Bridge Exploit

💸 $190,000,000 🔒 Improper Initialization (trusted root = bytes32(0)) 📅 2022-08-01 00:00:00 +0000

A routine upgrade set the trusted root to bytes32(0), making every message hash valid. Hundreds of attackers simply copied the first exploit transaction to drain $190M in hours.

Background

Nomad was a cross-chain messaging bridge that allowed assets to move between Ethereum, Avalanche, Moonbeam, and other chains. Messages from one chain were validated using a Merkle root — a cryptographic commitment to a set of valid messages.

A routine upgrade to the contract introduced a catastrophic initialization bug.

The Vulnerability

During the upgrade, the "committed root" — the trusted Merkle root against which messages were validated — was accidentally set to bytes32(0). The message processing code checked if a message was proven against an acceptable root.

The problem: messages[anyUnknownHash] returns 0 (the default mapping value in Solidity). And bytes32(0) was now a trusted root. So every message was valid.

🚨 Vulnerable Message Processor
function process(bytes memory _message) public returns (bool) { bytes32 _messageHash = _message.keccak(); // messages[unknown hash] returns 0 by default in Solidity // acceptableRoot(0) returns true because 0x000... was set as trusted! require( acceptableRoot(messages[_messageHash]), "!proven" ); // Attacker just needed to craft a message claiming to bridge tokens // Any message hash would pass the require above _process(_message); }

What Happened

  1. One attacker noticed the vulnerability and crafted a transaction claiming to bridge $100M USDC to themselves
  2. The transaction succeeded because the zero-root made any message valid
  3. Other observers on Etherscan and the mempool noticed the transaction
  4. They simply copied it, changing only their own recipient address — no hacking skill required
  5. Within hours, hundreds of "copycats" drained the remaining $90M
  6. It became the most "democratic" exploit in DeFi history — literally anyone could participate

Impact

$190M drained in hours. The bridge was completely emptied. Unlike Euler Finance, the Nomad exploit funds were largely not returned. The protocol shut down and has not relaunched.

Lessons Learned

⚠️ Never Trust Default Values

The zero value (0, false, address(0), bytes32(0)) is the default in Solidity. Using defaults as "trusted" or "valid" states is a critical anti-pattern.

  • Verify initialization values: After every upgrade, verify that critical security parameters are set correctly — not just set
  • Never use zero as a trusted value: Default Solidity values (0, false, 0x0) should never be treated as valid or trusted
  • Add post-upgrade invariant checks: Write automated tests that assert critical security invariants after each upgrade
  • Timelocks on upgrades: A timelock would have given the community time to spot the misconfiguration before funds were at risk
  • Monitor your contracts: Real-time monitoring would have caught the first exploit transaction and allowed emergency pausing