Nomad Attack Analysis
1. Attack information
Attack Transactions
0x96a2cfadb19cac9ba2a21c1f32b6f21b6132afe1b4f4698dee563ca01ec20e01 transfer USDC
0x811a54270e38154c30e65bdb8ee484883f5a9659dd16f1a335f9ff8f7eee342f transfer WETH
0x12efbc03ecc8d56ebbadd637dbfc30c0d709b463e2c831034233427dfaecf777 transfer FRAX
0xc78bc10c77ca92693513e5b97b4a0063167ca8488b019f6b740bc9f8cad103bf transfer CQT
0x8c65b2b25be2c929d86f1fb556aa0b981bd54c2ad54d8de63d3876c4c833293e transfer DAI
0x5304e01a4cb926523f681bc34a8b71a1dfae35b5a5159f78aecc7f685f42422c transfer CARDS
Attacked Contract
0xB92336759618F55bd0F8313bd843604592E27bd8 Replica
Analysis of the above attack transaction shows that the attacker performed the same process of transferring funds from the Nomad:ERC20 Bridge 10 times during the transaction.
The Replica.process() method is called on each call to the process and the parameters passed in are basically the same, the only difference being the address of the generated contract at that time.
Here the process method annotation makes it clear that the method checks whether the incoming message contains an acceptable Merkle root, and backs out if the message is not proven. The main judgement here is at line 185.
The parameter passed in here is 0x000…. .000, following up with the method acceptableRoot()
In the above judgement, neither of the first two if judgement conditions will hold, passing in _root as zero, and both LEGACY_STATUS_PROVEN and LEGACY_STATUS_PROCESSED have been set to non-zero in the Replica contract.
Core of the attack
Since the Replica contract is initialized with a zero value passed to _committedRoot, this results in subsequent contracts where confirmAt[0x000… .000] are all true. The condition can be passed directly in the process method, so that every message is proven valid by default and anyone can send a constructed message to transfer funds.
Summary and recommendations
Until now, more than $180 million has been transferred to Nomad. Through this attack, it appears that the contract deployer did not assign targeted values to the incoming parameters when initializing the contract, but instead passed in all-zero values, resulting in some of the judgment conditions in the contract remaining true, allowing anyone to transfer funds by sending constructed messages.
Security advice
- It is recommended that the contract is initialized with a targeted assignment of incoming parameters to avoid the risk of a contract with zero incoming parameters by default.
- Contract developers are advised to rigorously validate risk cues in audit reports to avoid contractual risks arising from inadvertent risk content.
New to trading? Try crypto trading bots or copy trading