The hacker/s used fake credentials at the front desk, gaining the trust of the receptionist; the receptionist then handed over the keys to the vault.
Incident Description
On Aug 10, 2021, PolyNetwork suffered a cross-chain attack that resulted in a total loss of $600M. The attacker created various malicious transactions on multiple chains and used the Relayer component to orchestrate the entire hack.
Using the vault example above, the hacker/s used fake credentials (invalid transaction on the Side Chain) to get a real key (signed Merkle certificate on the Alliance Chain) from the front desk receptionist (Relayer).
This report presents the full attack through an analysis and compartmentalization of the malicious transactions and the off-chain Relayer.
The note from the Mr. 600 Million White Hat indicates that CertiK’s analysis finds the missing piece of this attack: https://etherscan.io/tx/0x42446ccc66bb48eac7bd905ae7d79708f303849802b280eb4d65770c1bfc0997
Vulnerability Analysis
- The hacker/s initialized a malicious transaction — “method of changing the keepers”, on the Side Chain.
- The malicious transaction was generated on the Side Chain without sufficient verification. The Relayer picked it up, included it in the Alliance Chain’s Merkle tree, and signed it. This may have been the reason why the hacker/s had to risk buying the Side Chain token through a CEX to generate the malicious transaction on the Side Chain.
- The hacker invoked the ECCM contract at the Target Chain with the valid Merkle proof from step 2, enabling the function to be executed and the keepers to be changed.
- After the keeper privilege was gained, the hacker then performed a series of transactions to unlock assets from their designated addresses.
Note that some other Poly Network relayer chains did not sync the malicious transaction, so the assets are untouched even if a similar contract source code is used on the Target Chains.
Detailed Analysis
1. The hacker/s initiated a transaction on the Side Chain at Aug-10–2021 09:32:32 UTC: https://explorer.ont.io/tx/F771BA610625D5A37B67D30BF2F8829703540C86AD76542802567CAAFFFF280C#
In the transaction json, we decoded the code section and obtained the parameter mapping below:
{
“args”: “010000000000000014a87fb85a93ca072cd4e5f0d4f178bc831df8a00b”
“eth-eccd”: “cf2afe102057ba5c16f899271045a0a37fcb10f2”
“bsc-eccd”: “11e2a718d46ebe97645b87f2363afe1bf28c2672”
“heco-eccd”: “11e2a718d46ebe97645b87f2363afe1bf28c2672”
“poly-eccd”: “7cea671dabfba880af6723bddd6b9f4caa15c87b”
“method”: “6631313231333138303933”
}
2. This transaction invoked a method “6631313231333138303933” whose signature is equal to 0x41973cd9 (the signature of putCurEpochConPubKeyBytes(bytes)). This transaction should not be a valid transaction, but was generated on the Side Chain and picked up by the Relayer and mined into the Alliance Chain block. This malicious transaction was included in a Merkle tree and signed. The Merkle tree was used to prove the existence of the transaction.
Cross-chain transaction: https://explorer.poly.network/tx/1a72a0cf65e4c08bb8aab2c20da0085d7aee3dc69369651e2e08eb798497cc80
3. This attack used the loophole in the transaction validity on the Side Chain and the Relayer, which may have been the reason why the hacker/s had to risk buying the Side Chain token through a CEX to generate the malicious transaction on the Side Chain. See below the screenshot of the hacker’s statement:
4. On the Target Chain, EthCrossChainManager.verifyHeaderAndExecuteTx() was triggered. The first parameter proof includes:
a. fromChainId: 3 (Side Chain)
b. toChainId: 2 (Target Chain)
c. toContract: 0xcf2afe102057ba5c16f899271045a0a37fcb10f2
d. method: 0x6631313231333138303933
e. args: 010000000000000014a87fb85a93ca072cd4e5f0d4f178bc831df8a00b
5. The function checked the proof and recognized the data as valid because it is indeed a part of the Merkle tree. Later, it triggered EthCrossChainManager._executeCrossChainTx() to execute a transaction with toContract (0xcf2afe102057ba5c16f899271045a0a37fcb10f2), method (0x6631313231333138303933) and args (010000000000000014a87fb85a93ca072cd4e5f0d4f178bc831df8a00b). The method has the same function signature (0x41973cd9) as putCurEpochConPubKeyBytes(bytes), so EthCrossChainData.putCurEpochConPubKeyBytes() was triggered and executed, and lastly, the consensus bookkeeper public key was successfully changed.
6. After the hacker/s changed the public key, they were able to unlock the assets as they pleased.
Transaction on Ethereum: https://etherscan.io/tx/0xb1f70464bd95b774c6ce60fc706eb5f9e35cb5f06e6cfe7c17dcda46ffd59581
Root Cause Summary
1. The malicious transaction was generated without strict verification on the Side Chain.
2. The Relayer picked up and processed the malicious transaction since it contains the “makeFromOntProof” event.
3. The Relayer published the transaction obtained from step 1 on the Alliance Chain.
4. A valid Merkle tree proof that includes the malicious transaction was generated from step 3 on the Alliance Chain. The Merkle tree definition is as follows (https://en.wikipedia.org/wiki/Merkle_tree):
Merkle tree wiki: Hash trees can be used to verify any kind of data stored, handled and transferred in and between computers. They can help ensure that data blocks received from other peers in a peer-to-peer network are received undamaged and unaltered, and even to check that the other peers do not lie and send fake blocks.
5. The ECCM contract validated that the transaction does exist on the Side Chain using the Merkle proof. Note that the full validation of the transaction should have been done before constructing the Merkle proof that will be sent to the Target Chain. As shown in the design doc:
The management contract fetches the block headers from chain A, verifies whether or not the cross chain parameters and the proof are valid, and then transmits the necessary information to chain B in the form of an event;
Thus, the Target Chain (i.e., chain B) should validate that the received information is undamaged and unaltered using the Merkle proof, while the transaction information should be verified before it is sent to the Target Chain.