Bridge Bug Tracker

A community-maintained collection of bugs, vulnerabilities, and exploits in cross chain bridges.

Layer 2s, the most popular scaling solution for Ethereum at the moment, would not be possible without bridges. By their nature, bridges have become honey pots for exploits, occasionally with critical severity impact. This repo consolidates past exploited bridge bugs along with various bridge security resources in one place in the hope of making bridges more secure. These resources can be used as a reference for developers, auditors, and security tool makers.

Contributing

If you would like to contribute, there are two ways to do so:

  1. Create a PR, filling in all of the necessary details yourself
  2. Create an issue with a link or description of the bug or common vulnerability. The repo maintainers will then fill out the relevant details in a PR.

Table of Contents

  1. Exploited Bugs
  2. Confirmed Bug Bounties
  3. Related Audits
  4. Common Ways to Go Wrong
  5. Keeping Up with Bridges
  6. Related Talks

The below table shows known bridge hacks since 2021. These hacks include exploits on non-layer 2 bridges as well. Non-layer 2 bridges have similar functionality to layer 2 bridges, so these exploits still provide valuable learnings.

       Date         Protocol Funds At Risk Root Cause References Code to Reproduce
2023-06-01 PolyNetwork $4.4M
compromised 3-of-4 multisigthe root cause was not a logical bug on the smart contract, but, most likely, stolen (or misused) private keys of 3 out of 4 of Poly network's keepers (off-chain systems controlled by the team)
debaud N/A
2022-10-07 BNB Bridge $586M
BSC has a special precompile to verify IAVL trees, which is buggyin proofInnerNode.Hash function, the value of Right is ignored if Left is not empty, so you were able to change the path yet the (path, nleaf) hash did not change.
twitter gist N/A
2022-08-02 Nomad $152M
custodian: transaction replay attack
acceptableRoot[address(0)] == true
Within the process() function is an assert (line 185) that validates that the message for the transfer is associated with a valid root. By default, a root for an unproven message would be 0x00.

In an upgrade to the protocol, Nomad decided to initialize the value of trusted roots to 0x00. While this is common practice, it also matches the value for an untrusted root, so all messages are automatically viewed as proven.
...
Medium twitter .sol
2022-06-24 Harmony's Horizon $100M
Private key compromisedthe bridge only used a 2 of 5 validation scheme. This means that only two blockchain accounts needed to be compromised for an attacker to approve any malicious transaction that they wished.

The Harmony Horizon bridge was exploited via the theft of two private keys. These private keys were encrypted with both a passphrase and a key management service, and no system had access to multiple plaintext keys. However, the attacker managed to access and decrypt multiple keys.
...
twitter .sol
2022-06-08 Optimism / Wintermute 20M $OP
multisig address replay on L2Wintermute provided OP an Ethereum (L1) multisig address that they had not yet deployed to Optimism (L2). Attacker replayed txs to deploy ProxyFactory on L2, using the address of "Gnosis Safe: Deployer 3", which was pre-EIP155, thus does not include chainid. Attacker then generate a massive amount of multisig contracts until finding the matching address
blog .sol
2022-03-29 Ronin $624M
Private key compromisedThe Ronin Network uses a set of nine validator nodes to approve transactions on the bridge, and a deposit or withdrawal requires approval by a majority of five of these nodes. The attacker gained control of four validators controlled by Sky Mavis and a third-party Axie DAO validator that signed their malicious transactions.
...
twitter .sol
2022-03-20 Li Finance $570K
allow calls to any contractsThe hack took advantage of our pre-bridge swap feature. Our smart contract allows a caller to pass an array of multiple swaps using any address with arbitrary calldata.

This design gave us maximum flexibility in what DEXs we could call and what methods we could call. This also allowed anyone to call other contracts, not just DEXs. Our contract checks to make sure that the result of the swap or swaps is enough tokens to continue the bridging operation.

The attacker started by passing a legitimate swap of a small amount followed by multiple calls directly to various token contracts. Specifically, they called the transferFrom method which allowed the attacker to transfer funds from users’ wallets that had previously given infinite approval to our contract for that specific token.

This worked because these calls were performed within the context of the contract, which had permission to transfer user funds. The attacker transferred these tokens into a separate wallet that he controlled.

Once the transfers were completed, the small amount swapped at the beginning was bridged, and the transaction was completed.
blog .sol
2022-02-06 Meter $4.3M
missing require(amount_from_calldata==msg.value) in deposit()The problem with this assumption is that Meter has two functions where users could make deposits: depositEth and the underlying ETH20 deposit function. The depositEth function fulfills this assumption and validates the amount of value in the transaction’s calldata, which is the value that will later be passed to the deposit function.

The other deposit function does not fulfill this assumption or check that msg.value equals the amount specified within the calldata. The attacker called this deposit function directly and passed it an arbitrary amount in the calldata. This value was then sent to the handler’s deposit function, enabling the attacker to drain value from the protocol.

Hundred Finance was affected by the attack because the local price of BNB.bsc was depreciated due to the hack. Exploiters were able to buy BNB.bsc at a discounted rate and use them as collateral for loans with Hunter Finance, who used the global Chainlink price for the assets. As a result, the attackers could drain uncompromised assets from the protocol. Two of the four opportunistic loans were repaid, leaving Hunter Finance out $3.3 million.
...
twitter
blog
source
.sol
2022-01-18 Wormhole $360M
debt issuer: fake verification attack
fake account to precompiled sysvar
The solana_program::sysvar::instructions mod is meant to be used with the Instructions sysvar, a sort of precompile on Solana. However, the version of solana_program that Wormhole used didn't verify the address being used.

This meant that you could create your own account which stored the same data that the Instructions sysvar would have stored, and substituted that account for the Instruction sysvar in the call to verify_signatures. This would essentially bypass signature validation entirely.
twitter solana
2022-01-28 Qubit Finance $80M
address(0).safeTransferFrom() does not revertthe contract did not use OpenZeppelin’s SafeERC20 library. If the contract had used this library, the exploit would not have been possible as the SafeERC20.safeTransferFrom function makes use of functionCall() (function from OpenZeppelin’s Address.sol contract) which verifies that the target address contains contract code. This is not the case with the 0 address.

The exploited contract used a modified safeTransferFrom() function which instead of making use of functionCall() to verify that the target address contained contract code, used the call() function directly. As the 0 address has no code at all, no code is run, and the call is completed successfully without reverting. As a result, the deposit function executed successfully but no real tokens were deposited. The Ethereum QBridge caught the Deposit event and interpreted it as a valid deposit of ETH. As a result, qXETH tokens were minted for the attacker on BSC.

By repeating this process multiple times, the attacker was able to build up a large amount of qXETH without depositing any real tokens into the protocol. The attacker then was able to convert these tokens into BNB, draining about $80 million in assets from the protocol.
...
rekt .sol
2022-01-18 Multichain
/Anyswap
$1.4M
a) fail to validate token, b) fallback does not revert, c) infinite approvaladdress _underlying = AnyswapV1ERC20(token).underlying(); It’s intended to unwrap the underlying token (“DAI”) from its anyToken wrapping (“anyDAI”). However, token now is now the attacker’s controlled contract. We can see in the debugger, that this contract now returns WETH as its “underlying asset”. Multichain failed here as this function should have checked if the token address is indeed a Multichain token

IERC20(_underlying).permit(from, address(this), amount, deadline, v, r, s); Originally, the expected result was that the underlying token’s (“WETH”) ERC20 contract permit() is called to approve the router’s (this) ability to withdraw an amount from the user’s (from) address, as the user supplied a signed transaction for that denoted by (v,r,s). However, WETH contract does not have a permit() function! WETH contract does have a “fallback function” that is called when a function is called but not found. WETH’s fallback function is deposit() that does nothing material in this case, but allows its calling function’s execution to continue as it does not fail.

TransferHelper.safeTransferFrom(_underlying, from, token, amount); Originally, we expected that if we got to this line it means the signature in the line above was verified and now we can use the approve granted by it to actually move the amount from the user to the router. However, the signature was not verified, as seen above. In theory, it should not be a problem, as although the attacker’s input should not have passed the signature validation, it did not approve the router access to transfer the funds on the victim’s behalf. However, Multichain’s dapp requested from all of its users a practically infinite approval sum. This insecure methodology is quite common in dapps, to save user expenses on gas. We had warned in the past that such behavior (we named it baDAPProve) can be hazardous in case of a rogue or a vulnerable dapp, and now this potential threat had materialized. By abusing this excessive approval, the function transfers the WETH amount from the victim account to the attackers’ controlled contract.
medium .sol
2021-08-11 PolyNetwork $611M
custodian: call relay attack
use cross-chain messages to call special contracts with hash collision
The core of this attack is that the verifyHeaderAndExecuteTx function of the EthCrossChainManager contract can execute specific cross-chain transactions through the _executeCrossChainTx function.

Since the owner of the EthCrossChainData contract is the EthCrossChainManager contract, the EthCrossChainManager contract can modify the keeper of the contract by calling the putCurEpochConPubKeyBytes function of the EthCrossChainData contract.

The verifyHeaderAndExecuteTx function of the EthCrossChainManager contract can perform user-specified cross-chain transactions by calling the _executeCrossChainTx function internally. So the attacker only needs to pass in the carefully constructed data through the verifyHeaderAndExecuteTx function for the _executeCrossChainTx function to execute the call to the EthCrossChainData contract PutCurEpochConPubKeyBytes function to change the keeper role to the address specified by the attackers.

After replacing the address of the keeper role, the attacker can construct a transaction at will and withdraw any amount of funds from the contract.
rekt
medium
.sol
2021-07-10 Chainswap $4.4M
require(signatory == signatures[i].signatory, "unauthorized");this shows the misunderstanding of signature verification as both signatory and r,s,v are provided by the user
twitter
rekt
.sol
2021-07-02 Chainswap $.8M
require(signatory == signatures[i].signatory, "unauthorized");this shows the misunderstanding of signature verification as both signatory and r,s,v are provided by the user
post-mortem .sol
       Date        Protocol References Vuln Exploit
2022-09-19 Arbitrum twitter
medium
postUpgradeInit function wipes slots 0,1 & 2 and sets the bridge and allowListEnabled slots to new values — but leaves sequencerInbox and the two booleans set by the intializer modifier empty.call the public initialize() function and set our own address as the bridge to accept all incoming ETH deposits … but only because of this gas optimization in the code from a month prior.
Once initialized the contact with our own bridge contract address, we can hijack all incoming ETH deposits from users attempting to bridge to Arbitrum via the depositEth() function
2022-06-07 Aurora blog, immunefi, source, disclosure
delegateCall to precompilesIn the exit to NEAR and exit to Ethereum precompiles, the contract address was hardcoded with disregard to how DelegateCall works. When someone calls the contract it comes from the address of the contract always, and not from the input. Also, since the balance is from the EOA and not the contract, there is no transfer of ETH. This results in the Aurora Engine scheduling a transfer from its NEP-141 ETH balance to the adversary while it has not received an ETH transfer.
Instead of removing the hardcoded contract address, given context, it turned out to be better to instead return an exit error if the address given does not match the inputs' address.
2022-02-02 Optimism github
writeup
The code for Suicide is directly modifying the stateObject's data.Balance field instead of checking UsingOVM and redirecting that modification to OVM_ETH
Contract balances were improperly zeroed during self-destruction, so that the contract address would still have a balance after it had been self-destructed. This could have enabled an attacker to run a loop which doubled the balance of a contract each time, resulting in massive inflation and issuance directly to the attacker.

2023-03 Optimism Bedrock - Sherlock

3 Highs 8 Mediums 10 Lows

2023-01 Optimism Bedrock - Sherlock

3 Highs
  1. Due to additional operations between gas check and gas use, malicious user can finalize other’s withdrawal with less than specified gas limit, leading to loss of funds
  2. Due to forwarded gas being silently reduced if exceeding 63/64th of total gasleft(), withdrawals with high gas limits can be bricked by a malicious user, permanently locking funds
  3. Due to presence of reentrancy guard on the function relayMessage, a malicious user can make users lose their fund during finalizing their withdrawal
11 Mediums
  1. Due to not checking the value of is_last, batcher frames are incorrectly decoded leading to consensus split (.go)
  2. Due to MAX_RESOURCE_LIMIT, censorship resistance is undermined and bridging of assets can be DOSed at low cost
  3. When decoding a deposit transaction JSON string without the "gas" field, a panic/runtime error is triggered due to a nil pointer dereference (.go)
  4. Due to incorrect gas factor of 16 instead of 4 for 0 value bytes, MigrateWithdrawal() may set gas limit so high for old withdrawals when migrating them by mistake and they can't be relayed in the L1 and users funds would be lost (.go)
  5. Due to missing function, contract with only IOptimismMintableERC20 interface is not compatible with StandardBridge
  6. Due to small size of the blockHeightLRU, attacker can replay blocks and eclipses a node from the P2P network (.go)
  7. Migration can be bricked by sending a message directly to the LegacyMessagePasser (.go)
  8. Challenger can delete a l2Output which is older than 7 days meaning withdrawals will stop working for even confirmed transaction
  9. Since depositTransaction does not enforce minimum gas limit, it is costly to the sequencer to process these txs without compensation
  10. Deposits from L1 to L2 using L1CrossDomainMessenger will fail and will not be replayable when L2CrossDomainMessenger is paused
  11. Due to the requirement that reproving can only be done on the same L2 block number, withdrawal transactions can get stuck if output root is reproposed

2022-12-15 connext - Spearbit

15 Highs, 19 Mediems

2022-10-28 zkSync V2 - C4 - report

2 Mediums
  1. diamondCut is not protected in case of governor’s key leakage
  2. BLOCK_PERIOD should be set to 12 secs instead of 13, which results in a transaction in the Priority Queue incorrectly expires 5.5 hours earlier than expected.

2022-10-18 LI.FI - Spearbit

8 Highs, 19 Mediums

2022-10-17 Connext Amarok - C4 - report

6 Highs, 20 Mediums

2022-10-04 optimismDrippie - Spearbit

1 Medium

2022-08-30 Connext - Spearbit

4 Critical, 16 Highs, 20 Mediums

2022-05-05 LI.FI - C4 - report

2 Highs, 13 Mediums

2021-08-30 Connext - C4 - report

5 Highs, 2 Mediums

The following details are from Quantstamp's Review of Bridge Hacks. There, Quantstamp provides a useful framework for analysing the security of a cross chain bridge. The framework focuses on the 5 different attack surfaces of the bridge:

1. The Custodian - Incorrect asset amount released with respect to the burnt tokens
- Assets released despite the debt token has not been burnt
- Asset transaction replay for a single burn transaction
2. The Debt Issuer - Incorrect amount of debt issued with respect to the deposited assets
- Debt token issued although the actual verification did not take place
- Anybody can issue debt tokens
3. The Communicator - Issues debt tokens although no assets have been deposited
- Issues no debt tokens although assets have been deposited
- Accepts fraudulent messages from a fake custodian or a debt issuer
- Does not relay messages
- The source contract does not emit events upon deposit/withdrawal
4. The Interface (could be fixed with "revoke approval") - Deposit from another account
- Execute any calls from any contract
5. The Network - 51% attack

For keeping up to date with the various layer 2 bridges (note: this doesn't include all cross-chain bridges), L2Beat provides a great overview. Their section for the most popular bridges outlines the risk analysis, technology used, and the addresses of the involved smart contracts.

Another great resource for analyzing bridges is "The Current State of Layer 2 Bridges" article by Dr. Andreas Freund. It provides a good summary on what bridges are, why they're important, and the most popular ones in production.

Given the amount of money stolen in bridge hacks, it's not surprising that there are a good amount of resources out there dedicated to bridge security. Here are some great related talks to watch:

  • EVM-to-EVM Chain Bridges: The Good, the Bad and the Ugly by Jan Gorzny, quantstamp video

  • Review of Cross-Chain Bridge Hacks by Jan Gorzny, quantstamp video, slides

  • Securing a Cross-Chain Bridge by Christopher Whinfrey, hop protocol video, slides

  • Pre-Crime: the future of omnichain security by Ryan Zarick, LayerZero Labs video, slides

  • Breaking down bridge security models by Layne Haber, Connext video, slides

  • Breaking bridges by Yoav Weiss, EF video, slides

  • The Bridge Risk Framework Seminar by Spearbit, video, blog

  • Bridge Security by Spearbit, intro, checklist