Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: contracts-bridge-exploit description: "Cross-chain bridge attack — message-validation bypass (Wormhole class), validator key compromise (Ronin / Harmony class), reentrancy on token-bridge claim, Merkle-proof forgery on optimistic bridges, signature replay across chains. Bridges have lost >$2B; understand why." allowed-tools: Bash Read Write metadata: when_to_use: "bridge cross-chain wormhole ronin harmony nomad multichain anyswap layerzero axelar relayer validator merkle optimistic" subdomain: contracts tags: defi, bridge, cross-chain, validator mitre_attack: T1190, T1565
Cross-Chain Bridge Attack
Bridges hold large pooled assets and execute messages from a separate trust layer. Architecturally fragile.
Bridge taxonomy
| Type | Trust model | Past exploits | |
|---|---|---|---|
| Lock-and-mint | Trusted validators sign mint events | Wormhole ($325M), Ronin ($600M), Harmony ($100M) | |
| Burn-and-redeem | Same | Multichain/Anyswap ($126M) | |
| Optimistic | Fraud proof window | Nomad ($190M) | |
| Light-client / zk | Cryptographic | (Fewer hacks, but newer) | |
| Liquidity-network (Hop, Connext) | Routers + relayers | Smaller individual exploits |
Attack classes
1. Validator-signature bypass (Wormhole, Feb 2022, $325M)
The Solana-side verify_signature function trusted the caller to provide a "verified" sigset account. Attacker passed a sysvar that wasn't a real sigset → unauthorized mint of 120k wETH.
Lesson: any verify function whose input is a struct fetched by address is bypassable if address-trust is missing.
2. Validator key compromise (Ronin, March 2022, $600M)
9-of-9 validators, 5 of which were operated by one entity. Sky Mavis got phished → attacker controlled 5 keys → arbitrary mint.
Lesson: validator decentralization isn't math, it's operational. Count distinct legal entities and key-storage methods, not just keys.
3. Merkle-proof forgery (Nomad, August 2022, $190M)
After an upgrade, the zero-hash was set as a "valid root" — any proof against root = 0 passed. The first attacker withdrew real funds; then thousands of others copy-pasted the same tx with their own address. ~$190M total.
Lesson: check initialize / upgrade scripts for accidental defaults — bytes32(0), address(0), 1 (often the most-tested value) becoming the trusted value.
4. Replay across chains (Multichain class)
Bridge tx signed for chain A is replayed on chain B because chainId wasn't bound into the signed message:
function claim(bytes32 nonce, uint256 amount, bytes calldata sig) external {bytes32 hash = keccak256(abi.encode(nonce, msg.sender, amount)); // ⚠ no chainIdrequire(ecrecover(hash, ...) == validator);token.transfer(msg.sender, amount);}// On chain A: legit claim// On chain B (same validator, same token): replay the same sig
5. Reentrancy on bridge claim
Claim transfers tokens THEN updates nonce-used mapping:
function claim(...) external {token.transfer(msg.sender, amount); // attacker token w/ callbackused[nonce] = true; // updated AFTER}// ERC777 / ERC1363 / malicious ERC20 callback → re-enter claim same nonce
6. Liquidity-pool draining via fee oracle (Hop / Across class)
Bridges using a quote-and-relay model price slippage via oracle. Manipulate the oracle → arbitrage drains liquidity:
Step 1: Borrow large position on chain AStep 2: Manipulate underlying spot price oracle that bridge fee usesStep 3: Initiate cross-chain transfer at the manipulated rateStep 4: Repay loan, profit = bridge fee discount on a large transfer
Bridge audit checklist (the bugs that broke real bridges)
- `verify_signature` trust chain: from where does the verifier load its trust roots? Can the caller spoof them?
- Validator set update: is there a quorum check on changing validators? Can a 51% rotate themselves out and stay in?
- Nonce vs chainId binding: does the signed message include destChainId?
- Claim state machine: nonce-used mapping written BEFORE external token call?
- Default values: any
address(0),bytes32(0),1in initialize that becomes a trusted value? - Pause / rescue functions: are they gated by a multisig that's actually distributed?
- Relayer trust: does the bridge trust relayer-provided data, or re-verify on-chain?
- Fee/rate oracle: TWAP or spot? Manipulable?
Tooling
# Foundry test against a fork — simulate the exact bridge flowforge test --fork-url $RPC --match-contract Bridge --fork-block-number 17000000# Slither — finds reentrancy + signature-replay heuristicsslither <project># Echidna — fuzz the bridge claim function with arbitrary calldataechidna-test test/Bridge.t.sol --config echidna.yaml# Tenderly — live simulation against deployed bridge contracts
OPSEC / scope
- Bridge attacks are direct financial theft. Never test against live bridges without explicit ownership/written authorization.
- Bug bounty bridges (Immunefi top payouts) are the legitimate channel — read the Immunefi scope, then test on a private fork.
- Disclose responsibly — bridge maintainers may need 24-72h to drain liquidity to a safe vault.
References
- Rekt.news — every major bridge hack post-mortem
- Trail of Bits "Cross-Chain Bridge Architecture" deep dive
- Wormhole / Ronin / Harmony / Nomad post-mortems (official)
- "How to Build a Secure Cross-Chain Bridge" — Chainlink Labs research