Blockchain bridges enable users to transfer assets or data from one blockchain to another. Since most blockchains are developed in isolated environments and employ different rules and consensus mechanisms, they cannot communicate natively, and tokens cannot move freely between them. Bridges are designed to connect blockchains, facilitating the transfer of information and tokens between them.
The following is a simplified control flow of a bridge:
To initiate a transfer, the user must first deposit assets or data into the source chain contract, which will emit a deposit event. The backend server can range from a simple centralized server that monitors the emitted deposit event, verifies the transaction, and initializes the cross-chain transaction, to a more complex relayer chain. In the latter case, a series of relay nodes synchronize and monitor the transaction data of the blockchain, and the consensus nodes of the relay chain verify the validity of the cross-chain transactions and trigger the execution of the corresponding transactions. Once the target chain receives the signal to execute the transaction, the assets are sent to the user.
Trusted bridges depend upon a central entity or system for their operations.They have trust assumptions with respect to the custody of funds and the security of the bridge. Users mostly rely on the bridge operator's reputation. Examples include Binance Bridge and Multichain.
Trustless bridges operate using smart contracts and algorithms. They are trustless, i.e., the security of the bridge “is the same” as that of the underlying blockchain. Examples include Connext Network and Hop Exchange.
L1 <> L1 Bridges: connect L1 blockchains with each other. For example, the Avalanche Bridge that connects Ethereum and Avalanche.
L1/L2 <> L2 Bridges: connect a certain baselayer with different L2 solutions and the L2s with each other. For example, Across is a bridge that connects Ethereum Mainnet to L2s like Arbitrum and Optimism. Hop Protocol connects different L2s with each other in addition to connecting the Ethereum Mainnet with them.
Lock & Mint: lock assets on the source chain and mint assets on the destination chain. Examples: Polygon’s Proof of Stake bridge, Avalanche Bridge, wrapped BTC, and wMonero.
Liquidity Pools: Liquidity pools on each chain. Users receive their assets from the liquidity pool on the destination chain. Examples: Hop, Across.
Atomic Swaps: swap assets on the source chain for assets on the destination chain. Generally more trustless because they rely on smart contracts for swaps and remove the trusted third-party necessary in lock&mint or Liquidity Pools. Examples: cBridge, Connext.
Chain-To-Chain Bridges: support the movement of assets between two blockchains. Generally, such bridges use the lock&mint mechanism. Examples: Native bridges such as Polygon’s PoS Bridge or Avalanche Bridge.
Multi-Chain Bridges: transfer assets across multiple blockchains. They are built such that they can be deployed to any L1 or L2 blockchain. Examples: Connext and cBridge.
Specialized Bridges: focused on specific ecosystems and designed to support the movement of assets. Due to specialization, they are generally faster and cheaper transactions.
Wrapped Asset Bridges: transfer of non-native assets to different blockchains. Creating wrapped assets on the destination representing the original asset on the source. Examples: wrapped BTC, wMonero.
Data Specific Bridges: interoperability protocols designed specifically for transferring arbitrary data across blockchains. Generally, these protocols become the base layer for dApps and make it possible for them to achieve cross-chain composability. Examples: Celer’s inter-chain Message Framework, IBC, Nomad.
dApp Specific Bridges: From a technical perspective, these are not bridges. By connecting to different blockchains, these dApps have built an ecosystem that allows value to be transferred across blockchains similar to bridges. Examples: Thorchain is a decentralized cross-chain AMM that offers cross-chain liquidity features that enable the exchange of assets across blockchains. Others: Anyswap, Wanchain, and Synapse.
Compared to other types of decentralized applications (dApps), blockchain bridges are more complex due to the greater number of components involved which result in a larger attack surface. Furthermore, the bridge contract often holds a substantial amount of assets as user deposits are stored within the contract. As a result, malicious actors are highly motivated to target cross-chain applications to steal large sums of funds. Bridge exploits can have significant implications for wrapped tokens as the loss of deposits may render the debt token worthless in extreme cases. In this article, we are examining several common vulnerabilities that have been exploited or that we have encountered during our audit.
The most crucial and must have process in a bridge system is validation. We can divide the validation into on-chain validation and off-chain validation.
For simple bridges, especially those designed for specific dApps, on-chain validation is kept to a minimum. These bridges rely on the centralized backend to execute basic operations like minting, burning, or transferring tokens, while all verifications are performed off-chain. In contrast, other types of bridges use smart contracts to validate messages. In this scenario, when a user deposits funds on one chain, the smart contract generates a signed message and returns the signature in the transaction. This signature serves as proof of deposit and is used to verify the user's withdrawal request on the other chain. This process prevents various security attacks, including replay attacks and users forging deposit records. If there is a vulnerability during the validation process, the attacker can cause pretty serious damage. The BNB token hub hack occurred due to such a vulnerability. The attacker exploited a vulnerability in the precompiled contract for IAVL proof validation. The attacker generated forged proofs by adding leaves containing the attacker's payload hashes to the IAVL tree and inner nodes to finish the proof verification early. The attacker bypassed the IAVL proof validation and successfully minted two million BNB to their account.
Users are required to provide basic information about the cross-chain transfer they wish to make, such as the token they want to swap and the target chain. Without proper validation, unexpected operations may occur.
In January 2022, Multichain (previously known as Anyswap) was exploited due to validation bypass. Multichain is a cross-blockchain router protocol designed to enable users to swap and exchange digital tokens across chains while reducing fees and streamlining the overall process. The router wraps the actual token with its wrapped “anyToken '' to achieve this. For example, the DAI token is wrapped as anyDAI. Conversely, DAI is the underlying asset of anyDAI. The wrapped token is used for internal accounting, and when a user “transfers'' DAI from Ethereum to BSC, anyDAI is added to the Multichain anyDAI BSC contract and subtracted from anyDAI Ethereum contract.
The vulnerable function is
anySwapOutUnderlyingWithPermit, which is used to swap an underlying token using the ERC20
permit function. The
permit function enables users to supply a signed transaction approving a contract to spend their funds without actually sending it to the blockchain, thereby minimizing their gas cost. The signed transaction is expressed in (v, r, s) terms.
The attacker passed the following arguments to the function: 'from' represents the address of the victim, while 'token' and 'to' represent the attacker's deployed contract and the destination address, respectively. However, the attacker did not provide a valid signature for the 'v', 'r', and 's' parameters.
In the vulnerable function, the first line reads
address _underlying = AnyswapV1ERC20(token).underlying();. The intention is to unwrap the underlying token, "DAI," from its AnyToken wrapping, "anyDAI." However, in this case, the token address is controlled by the attacker, and the contract returns WETH as its "underlying asset." WETH is not a wrapped token defined within the project.
The second line reads,
IERC20(_underlying).permit(from, address(this), amount, deadline, v, r, s);. WETH contract does not implement the
permit function. Instead, WETH has a fallback function called
deposit, which does not perform any actions but allows the calling function to execute without failing.
TransferHelper.safeTransferFrom(_underlying, from, token, amount); would transfer tokens from the victim to the attacker-controlled contract. To execute this transfer, users must approve the router contract to spend an unlimited amount of their tokens. In this case, Multichain's dApp requested a practically infinite approval sum from all users, a common but insecure practice in dApps to save user expenses on gas.
By exploiting this excessive approval and a lack of input validation, the attacker was able to transfer the WETH amount from the victim's account to their controlled contract.
In a bridge system, the off-chain backend server typically assumes a centralized role in verifying the legitimacy of messages sent from the blockchain. This validation process typically focuses on verifying deposit transactions. The backend server must ensure that the deposit transaction it processes has actually occurred and has not been used for withdrawal on the target chain. As the backend server typically holds the privilege to determine whether a user can withdraw on the target chain, it is a high-value target for attackers. Two projects, QANX and Cennznet, have both been exploited due to attacks on their backend servers.
QANX serves as a bridge between the Ethereum (ETH) and Binance Smart Chain (BSC) networks, with the graph below illustrating the workflow of the bridge.
At a high level, the bridge operates as follows:
The issue here is that when the backend server verifies the transaction, it only validates the structure of the emitted event of the transaction. It does not verify the contract address that emitted the event. Consequently, an attacker could deploy a malicious contract and create a transaction to forge a deposit event that has the same structure as a legitimate deposit event. The attacker could then send the transaction hash to the backend, bypassing verification and allowing them to withdraw from the target chain.
The hack on the Cennznet bridge is very similar to the QANX hack. In this case, the attacker deployed a contract to emit a deposit event, which the backend server picked up, allowing the attacker to drain funds from the bridge contract.
Validation is a critical aspect of the bridge system, and bypassing it can lead to significant financial losses, as illustrated above. Since each bridge has unique verification requirements, it is challenging to provide general guidance on ensuring the verification process is error-free. The most effective approach to preventing verification bypass is to test the bridge thoroughly against all possible attack vectors and ensure that the verification logic is sound.
Bridges take different approaches to managing the distinction between native tokens and utility tokens. For example, on the Ethereum network, the native token is ETH, and most utility tokens adhere to the ERC-20 standard. When a user intends to transfer their ETH to another chain, they must first deposit it into the bridge contract. To achieve this, the user simply attaches the ETH to the transaction, and the amount of ETH can be retrieved by reading the msg.value field of the contract.
Depositing ERC-20 tokens differs significantly from depositing ETH. To deposit an ERC-20 token, the user must first approve the bridge contract to spend their tokens. Then, when depositing the token to the bridge contract, the contract will either burn the user's token using the burnFrom function or transfer the user's token to the contract using the transferFrom function. Due to this distinction, the bridge contract must handle these two types of deposits separately. One approach is to use an if-else statement within the same function, while another method is to create two separate functions to handle each scenario. It is critical to consider what would happen if the user attempts to use the wrong function, such as attempting to deposit ETH using the ERC-20 deposit function, as seen in the Qubit hack.
The Qubit bridge functions to handle user deposits via two methods:
deposit for ERC-20 token cross chain requests and
depositETH for handling ETH transactions. In the attack on the bridge, the attacker exploited a vulnerability by calling the
deposit function with specific parameters.
The crucial point to note is that the
resourceID is set to "0x00000000000000000000002f422fe9ea622049d6f73f81a906b9b8cff03b7f01". The contract utilizes the resourceID to identify the type of token the user wishes to transfer cross-chain. The token address can be accessed via mapping
resourceIDToTokenContractAddress. The resourceID used in the deposit function maps to the 0 address, which corresponds to ETH in the contract. Therefore, the attacker attempted to exploit the
deposit function to handle ETH deposits, which was an unintended behavior. The verification process is carried out in the
deposit function in the QBridgeHandler contract, as depicted below:
deposit function in the Qubit bridge, which is designed to handle ERC-20 token cross chain requests, has two checks. Firstly, it requires the token address to be whitelisted (line 128). Secondly, it requires the user to transfer tokens to the contract (line 135). In the attack, the resourceID passed into the deposit function maps to the zero address, which represents ETH in the contract. There is no safeTransferFrom function implemented in the zero address. When the user tries to transfer tokens to the contract, the external call returns false, but the transaction can continue to execute. Unfortunately, even if line 135 returns false, the contract does not handle the case, allowing the transaction to be successfully executed.
In most blockchain bridges, there is a privileged role responsible for managing token and address whitelisting/blacklisting, assigning/changing signers, and other critical configurations. It is crucial to ensure that all configurations are accurate, as even seemingly trivial oversights can lead to significant hacks. In fact, some of the most significant hacks in recent years have resulted from such oversights.
For instance, in August 2022, the Nomad bridge was exploited, resulting in a loss of approximately $190M worth of assets. The bridge contract contains a
process function that validates whether the transfer message is associated with a valid root. However, by default, the root for an unproven message would be 0x00.
A few days prior to the hack, a protocol upgrade took place where the trusted roots' value was initialized to 0x00. Although this is a common practice, in this particular case it also matches the value for an untrusted root. As a result, all messages are automatically considered proven, allowing anyone to submit an arbitrary message and pass the verification.
Due to their high value, cross-chain bridges have long been the focus of attackers. We expect more attacks of this nature in the future. However, teams can strengthen their security measures by conducting thorough testing and engaging in third-party audits, reducing the risk of the devastating hacks that have plagued bridges over the last few years. Bridges are critical in a multi-chain world, but security must be a primary concern when designing and building effective Web3 infrastructure.