Poly Network Incident Observations

The following steps are used by Inspex to understand the incident that happened to Poly Network. In this article, we will describe how we investigated the situation from the transaction and our brief observations, not the analysis of the vulnerability or the attack.

For the attack detail, please see in the following tweet (very clever):
https://twitter.com/kelvinfichter/status/1425217046636371969

The following transaction was used to analyze the incident:
https://www.bscscan.com/tx/0xd59223a8cd2406cfd0563b16e06482b9a3efecfd896d590a3dba1042697de11a

Analyzing Steps

1. Inspex team started the investigation from the address that sent tokens back to the attacker (0x2f7ac9436ba4b548f9582af91ca1ef02cd2f1f03) and found that it is a Lock Proxy contract according to https://github.com/polynetwork/docs/blob/master/config/README.md.

2. Using Tenderly Debugger (https://dashboard.tenderly.co/tx/bsc/0xd59223a8cd2406cfd0563b16e06482b9a3efecfd896d590a3dba1042697de11a/debugger?trace=0.4), Inspex Team found that a function with the signature “0x06af4b9f” was called in 0x2f7ac9436ba4b548f9582af91ca1ef02cd2f1f03.

3. From the source code of LockProxy contract, we checked the signature “0x06af4b9f” of the function called and found that it is the unlock() function (https://github.com/polynetwork/eth-contracts/blob/d16252b2b8/contracts/core/lock_proxy/LockProxy.sol#L102-L119). This function is used to mint tokens in the destination chain according to the amount burned from the bridging which is used by the attacker to drain the locked assets.

4. However, the unlock() function can be called by the address in managerProxyContract only, and it is stored in storage slot 1 of the LockProxy contract (0x2f7ac9436ba4b548f9582af91ca1ef02cd2f1f03).

5. By observing storage slot 1, we found that the stored address is 0x7cea671dabfba880af6723bddd6b9f4caa15c87b that matches with EthCrossChainManager contract by comparing their function signatures (https://github.com/polynetwork/eth-contracts/blob/master/contracts/core/cross_chain_manager/logic/EthCrossChainManager.sol)

6. From Tenderly, we found that the function with the signature “0xd450e04c” was called. By looking up the signature, we found that it is the verifyHeaderAndExecuteTx() function (https://github.com/polynetwork/eth-contracts/blob/master/contracts/core/cross_chain_manager/logic/EthCrossChainManager.sol#L127-L173)

7. If every conditions inside the verifyHeaderAndExecuteTx() function are fulfilled, the _executeCrossChainTx() function will be executed, allowing the execution of other contract functions through the EthCrossChainManager contract.

8. After an analysis in the previous step, we found that verifyHeaderAndExecuteTx() function will verify a signature in the headerSig parameter using ECCUtils.verifySig() function.

9. ECCUtils.verifySig() function will verify that the header has been signed by n — ( n — 1) / 3 node of Poly chain consensus (n is the number of keys that has been defined in the contract)

10. For the keepers, these keepers have been stored in contract at 0x11e2A718d46EBe97645b87F2363AFE1BF28c2672 which is the EthCrossChainData contract (https://github.com/polynetwork/eth-contracts/blob/master/contracts/core/cross_chain_manager/data/EthCrossChainData.sol) in the ConKeepersPkBytes state variable.

11. After inspecting ConKeepersPkBytes, we got a value 0x010000000000000014a87fb85a93ca072cd4e5f0d4f178bc831df8a00b00003a which begins with 8-byte data that represents a number of keeper, 0100000000000000 can be converted to 1 with Little-Endian conversion.

12. The contract owner of EthCrossChainData contract (0x11e2A718d46EBe97645b87F2363AFE1BF28c2672) is the same with LockProxy contract that is EthCrossChainManager contract (0x7cea671dabfba880af6723bddd6b9f4caa15c87b).

Observations

  • Only the people who own the private key of the public key set in the EthCrossChainData can pass the validations in the verifyHeaderAndExecuteTx() function.
  • The keeper can be set using the putCurEpochConPubKeyBytes() function, which can only be executed by the contract owner (https://github.com/polynetwork/eth-contracts/blob/master/contracts/core/cross_chain_manager/data/EthCrossChainData.sol#L45-L48).
  • The contract owner of EthCrossChainData contract is also the EthCrossChainManager contract. This means that anyone who can send the cross-chain transaction through the EthCrossChainManager contract, is able to execute putCurEpochConPubKeyBytes() function and change the keeper value (public key) in theConKeepersPkBytes state.
  • After the incident, In the EthCrossChainData contract, there was only 1 keeper stored. This means that the verifyHeaderAndExecuteTx() function can be called using just one holder of the private key.

About Inspex

Inspex is formed by a team of cybersecurity experts highly experienced in various fields of cybersecurity. We provide blockchain and smart contract professional services at the highest quality to enhance the security of our clients and the overall blockchain ecosystem.

For any business inquiries, please contact us via Twitter, Telegram, contact@inspex.co

Cybersecurity professional service, specialized in blockchain and smart contract auditing https://twitter.com/InspexCo