Skip to content

deficollective/layerzero-centralization-poc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LayerZero Centralization PoC

This repository contains a Proof of Concept (PoC) demonstrating potential centralization risks in LayerZero's architecture for specific OApps and OFT apps. This repository accompanies the blog article published on defiscan's website: layerzero-centralization-poc.

This project was setup with Foundry. The repository contains scripts which are grouped through numeration. The scripts for initialising the OFT are 0.x, while the scrips starting from 1 through 5 are steps to show the capturing and exploitation of the OFT token relying on the configurability of the single OApp/OFT settings.

Summary

In summary, the OFT contract will be deployed (script 0.1) with default DVN configurations first (no need to explicitly specify the default DVN config). Next, the OFT owner is required to specify peers (destination contracts on the other chains) and libraries used for sending and receiving messages (scripts 0.2 and 0.3). The scripts 0.4 and 0.5 show normal OFT operations to demonstrate the normal flow.

The OFT's security settings can be changed by the delegate of the OFT contract. To change the delegate of the OFT contract, the owner of the OFT contract can call setDelegate(...). So script 1 transfers ownership and sets the malicious EOA as delegate. In script 2 malicious configurations are set for the OFT contract, which means the malicious EOA becomes the only configured DVN. With the EOA being able to submit verification (script 3) and their committment (script 4), the malicious actor can mark payloads with malicious intend as executable. The execution with script 5 is permissionless and requires a call to the endpoint with the payload that resolves to the same hash as the verification.

Setup (scripts 0.1 through 0.3)

Deployment of OFTs (script 0.1)

forge script script/0.1_Deploy.s.sol --rpc-url $MAINNET_RPC --broadcast --etherscan-api-key $ETHERSCAN_API_KEY --verify  -vvvv
forge script script/0.1_Deploy.s.sol --rpc-url $ARBITRUM_RPC --broadcast --etherscan-api-key $ETHERSCAN_API_KEY --verify  -vvvv

demo deployments (referenced in the blog article):

Token ownership looks currently like this:

Step Owner Chain Balance
1 0x3857FB500e931B74674a905c0ee51308d56E2b19 Ethereum 1000 COFT
1 0x3857FB500e931B74674a905c0ee51308d56E2b19 Arbitrum 0 COFT

Set Libraries on Ethereum and Arbitrum (script 0.2)

Libraries are used for message encoding, handle verification and committment by DVNs. Libraries are approved by Layer Zero and registered with the Endpoint. Their docs describe the process for OApp and OFT deployers as following:

  1. Choose your libraries (addresses of deployed MessageLib contracts). For standard cross-chain messaging, you should use SendUln302.sol for setSendLibrary(...) and ReceiveUln302.sol for setReceiveLibrary(...). You can find the deployments for these contracts under the Deployments section.
  2. Call setSendLibrary(oappAddress, dstEid, sendLibAddress) on the Endpoint.
  3. Call setReceiveLibrary(oappAddress, srcEid, receiveLibAddress, gracePeriod) on the Endpoint.

So we set the standard libraries on both Ethereum Sepolia and Arbitrum Sepolia.

forge script script/0.2_SetLibrariesEthereum.s.sol --rpc-url $MAINNET_RPC --broadcast -vvvv
forge script script/0.2_SetLibrariesArbitrum.s.sol --rpc-url $ARBITRUM_RPC --broadcast -vvvv

demo transactions:

Ethereum Sepolia

Arbitrum Sepolia

Set Peers (script 0.3)

Peers are the only contracts that can be used to send transactions from on the source chain and peers are the only contracts that can receive transactions on the destination chain. However, with a malicious DVN, we just target the destination chain (could be any registered chain) and just specify the message came from the peer on the source chain, even though the transaction never occurred on the source chain.

forge script script/0.3_SetPeers_Ethereum.s.sol --rpc-url $MAINNET_RPC --broadcast -vvvv
forge script script/0.3_SetPeers_Arbitrum.s.sol --rpc-url $ARBITRUM_RPC --broadcast -vvvv

demo transactions:

Demo of normal OFT operations (Scripts 0.4 and 0.5)

This part is to show normal functionality.

Bridge 1000e18 tokens from mainnet to arbitrum:

forge script script/0.4_Demo_Normal_Tx_Mainnet_to_Arbitrum.s.sol --rpc-url $MAINNET_RPC --broadcast -vvvv

Send call on OFT (Mainnet Sepolia Tx):

https://sepolia.etherscan.io/tx/0xc4fc209f2c778c9609f4ecd85131f778b11e03e6866696cc259e6f3275b1fd26

execute302 call (Arbitrum Sepolia Tx):

https://sepolia.arbiscan.io/tx/0x9a3c2b7dc6212ca62acbe44d33e958ffc82e67955770c0a5acab9d65b3fd1fab

And send 500e18 tokens back to mainnet:

forge script script/0.5_Demo_Normal_Tx_Arbitrum_to_Mainnet.s.sol --rpc-url $ARBITRUM_RPC --broadcast -vvvv

demo transactions:

Send call on OFT (Arbitrum Sepolia Tx):

https://sepolia.arbiscan.io/tx/0x0d6126f09787552c35c4c78475dea82f51882c4532f02a2a761bb4166ce6bbfb

execute302 call (Ethereum Sepolia Tx):

https://sepolia.etherscan.io/tx/0x5c37adac11882cbc69cafb836c97a0c1d88c75087b122fb597e2b80f068bda79

To summarize:

Step Owner Chain Balance Total in circulation
1 0x3857FB500e931B74674a905c0ee51308d56E2b19 Ethereum 1000 COFT 1000 COFT
1 0x3857FB500e931B74674a905c0ee51308d56E2b19 Arbitrum 0 COFT 1000 COFT
2 0x3857FB500e931B74674a905c0ee51308d56E2b19 Ethereum 0 COFT 1000 COFT
2 0x3857FB500e931B74674a905c0ee51308d56E2b19 Arbitrum 1000 COFT 1000 COFT
3 0x3857FB500e931B74674a905c0ee51308d56E2b19 Ethereum 500 COFT 1000 COFT
3 0x3857FB500e931B74674a905c0ee51308d56E2b19 Arbitrum 500 COFT 1000 COFT

Exploitation (Scripts 1 through 5)

Capture (Script 1)

This step simulates a compromise of the token ownership. The owner of the token transfers ownership to a malicious address. This happens only on Ethereum (Sepolia).

forge script script/1_Capture_OFT.s.sol --rpc-url $MAINNET_RPC --broadcast -vvvv

demo transactions:

transfer ownership: 0x4194774aef7111988eeb6c1ca062097ff553669302f5c825f527232aa8721989

set delegate: 0x43b66ae30b6b366be3d0048bd2777938cba5eecc73f4e357fd3a7869fedbdb3f

Custom Receiving Config (Script 2)

The project was setup with default receive config, which helped us to relay the crosschain token transfer in step 0.4 and 0.5. To trigger malicious behavior on Ethereum Sepolia, we set a custom receive config on Ethereum Sepolia. The malicious EOA will verify and confirm a transaction that never happened on Arbitrum Sepolia.

forge script script/2_Custom_SetReceiveConfig_malicious.s.sol --rpc-url $MAINNET_RPC --broadcast -vvvv

Demo transaction:

0xf3db5c1921a9fe7f52235cbafd2649a5167489b6db86baa5c00e405de7ee09f1

Check:

EndpointV2.getConfig() on Ethereum Sepolia shows now that the Attacker EOA is the only registered DVN.

Malicious Verification (Script 3)

With the malicious DVN, we can verify an incoming transaction that should never happen. We create a packetHeader, which specifies the OFT on Arbitrum as sender, the OFT on Mainnet as receiver, alongside the endpoint ids of both chains, and the nonce of this unique crosschain channel between the two apps (peers). Additionally, we craft a payload and hash it. The payload just needs to conform with the abi encoding of the destination app (OFT on Mainnet). With the malicious intend, we specify the amount we want to mint ourselves and the address of the attacker as beneficiary.

forge script script/3_Malicious_Verification.s.sol --rpc-url $MAINNET_RPC --broadcast -vvvv

Demo transaction 3_Malicious_Verification.s.sol:

0x537f887bf58eaa4975f42cb96de5825c4af0898cea0e29ac723133328f3cb2a3

Malicious Committment (Script 4)

After all DVNs have verified like in script 3, the verification can be committed. The receiving library checks if all the configured DVNs have confirmed the header and payloadHash. As a consequence of this call, the payload hash is stored on the Endpoint on Ethereum Sepolia inside the inboundPayloadHash mapping. Calling lzReceive with our malicious payload hash will be checked against this payload hash and is equal, and thus will be executed.

forge script script/4_Malicious_Commitment.s.sol --rpc-url $MAINNET_RPC --broadcast -vvvv

Demo transaction 4_Malicious_Commitment.s.sol:

0xc2bae285c1232ec8329343379857c191c8c701c95278942652680e500db067b9

Steal (Script 5)

Now we can finally execute the malicious payload on Mainnet Sepolia.

forge script script/5_Steal.s.sol --rpc-url $MAINNET_RPC --broadcast -vvvv

Demo transaction 5_Steal.s.sol:

0x44196a9b06dcb79a41f943cd9a4f4c2358739400b57e1de2c49145b3d9c873e2

Step Owner Chain Balance Total in circulation
1 0x3857FB500e931B74674a905c0ee51308d56E2b19 Ethereum 1000 COFT 1000 COFT
1 0x3857FB500e931B74674a905c0ee51308d56E2b19 Arbitrum 0 COFT 1000 COFT
2 0x3857FB500e931B74674a905c0ee51308d56E2b19 Ethereum 0 COFT 1000 COFT
2 0x3857FB500e931B74674a905c0ee51308d56E2b19 Arbitrum 1000 COFT 1000 COFT
3 0x3857FB500e931B74674a905c0ee51308d56E2b19 Ethereum 500 COFT 1000 COFT
3 0x3857FB500e931B74674a905c0ee51308d56E2b19 Arbitrum 500 COFT 1000 COFT
4 0x3857FB500e931B74674a905c0ee51308d56E2b19 Arbitrum 500 COFT 1500 COFT
4 0x3857FB500e931B74674a905c0ee51308d56E2b19 Ethereum 500 COFT 1500 COFT
4 0x9D96e3141CB6D53d3A69e7Cc8Fe4237B0f728d5F Ethereum 500 COFT 1500 COFT

We minted new tokens on mainnet, that were not burnt on Arbitrum. We can repeat this step and also technically could mint any amount of tokens.

How this project was setup

Create Forge Project

forge init

forge install layerzero-labs/devtools
forge install layerzero-labs/LayerZero-v2
forge install OpenZeppelin/openzeppelin-contracts
git submodule add https://github.com/GNSPS/solidity-bytes-utils.git lib/solidity-bytes-utils

Configure Foundry Project

[profile.default]
src = "src"
out = "out"
libs = ["lib"]

remappings = [
    '@layerzerolabs/oft-evm/=lib/devtools/packages/oft-evm/',
    '@layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/',
    '@layerzerolabs/lz-evm-protocol-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/protocol',
    '@layerzerolabs/lz-evm-messagelib-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/messagelib',
    '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/',
    'solidity-bytes-utils/=lib/solidity-bytes-utils/',
]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

.env

Address/private key combination of two accounts are needed to be supplied (regular owner and attacker). After deployment of OFTs their addresses need to be added. Also RPC urls need to be supplied and etherscan API key for smart contract verification.

If the same chains are used (Ethereum Sepolia and Arbitrum Sepolia), then no further variables need to be stored/modified.

EIDs and library addresses can be found here: https://docs.layerzero.network/v2/deployments/deployed-contracts?stages=testnet&chains=arbitrum-sepolia

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published