The Attack on Nomad: A Deep Dive
Fairyproof Conducts a Deep Dive on the Attack on Nomad
Fairyproof Conducts a Deep Dive on the Attack on Nomad
On August 1, 10:00PM UTC time, a cross-chain bridge Nomad suffered a devastating attack which resulted in a loss of over US$150 million, drained within three hours.
As of today, this was one of the largest losses that have ever been witnessed in the crypto space.
More than 700 addresses were involved in the attack — Three of which were in the Ethereum chain and had caused big losses to the bridge.
The attack from 0xb5c55f76f90cc528b2609109ca14d8d84593590e started at 21:32 and ended at 23:34.
The attack from 0x56D8B635A7C88Fd1104D23d632AF40c1C3Aac4e3 started at 21:55 and ended at 23:38.
The attack from 0xBF293D5138a2a1BA407B43672643434C43827179 started at 22:00 and ended at 23:14.
The first two addresses were created 40 days prior to the incident and had attained their gases to launch their attacks from Tornado.Cash.
Both addresses proceeded to attack the bridge by directly interacting with it without the use of an attacking contract.
In addition, Fairyproof found out that these two addresses have a long history of doing arbitrage transactions with various DeFi applications on Ethereum. For instance, on July 11, the second address (0x56D8B6…) sent 20 ETHs to the first address (0xb5c55f…). The first address then used these 20 ETHs to conduct arbitrage transactions for profits.
Based on these activities and behaviour, it is likely that these two addresses belong to the same hacker.
The third address was created 6 days before the incident and had attained its gas to launch its attack from Tornado.Cash. It only began its attack after the first two addresses had conducted theirs. The third address had deployed an attacking contract and used it to attack the bridge.
The bridge had two major issues that were leveraged by the attackers. The first is a vulnerability found in Nomad’s Replica contract, and the other is an inappropriately set parameter during a contract upgrade.
Three functions were involved revealing the vulnerability in Nomad’s Replica’s implementation contract: “process”, “initialize”, and “acceptableRoot”.
In a contract upgrade, Nomad’s admin upgraded the Replica contract and initiated an “initialize” transaction to set four parameters including “_commitedRoot” to 0 resulting in the map variable “confirmAt” ’s key value to be 1 when the key was 0, i.e. “comfirmAt[0] = 1”.
The “process” function verified a withdrawal request’s validity by calling the “acceptableRoot” function to verify the request’s hash value.
Here is the trick: a fake message’s value is 0. So when a fake message was passed to the contract for verification, the input parameter “_root” of the “acceptableRoot” would be 0. Then “acceptableRoot” would execute “_time = confirmAt[_root]” as “_time = confirmAt[0]”. Since “confirmAt[0]” was set to 1 in the aforementioned initialization, “_time” would be set to “1” and this resulted in the value of “block.timestamp ≥ 1” to be returned.
This scenario allowed anyone to easily copy and paste a transaction that had gone through, replace another’s address within said transaction with yours (or the attacker), resend the transaction request, and be able to drain the bridge.
When the vulnerability was revealed, many other attackers joined in the exploitation and the bridge was drained for a large amount within a short span of time.
Three lessons must be learnt from this incident:
In Solidity, when a map variable’s key does not exist, the value mapped to the key will be set to 0.
Consider testing all the input parameters for a function and make sure the parameters’ default values would not introduce risks.
Once again, a Reminder for Project Teams: Always test thoroughly and do smart contract audits before deploying smart contracts on-chain.
Additional Details:
The Replica’s proxy contract was deployed at:
0x5D94309E5a0090b165FA4181519701637B6DAEBA. (Ethereum)
Its implementation contract was deployed at:
0xB92336759618F55bd0F8313bd843604592E27bd8. (Ethereum)
The hash value of the initialization transaction was:
0x53fd92771d2084a9bf39a6477015ef53b7f116c79d98a21be723d06d79024cad (Ethereum)