Unchecked deposit_token Allows Malicious Token Substitution During Withdrawals
Summary
A verified withdrawal message can be maliciously intercepted, replacing the expected token with a different, low-value token (e.g., BONK) by changing the deposit_token account. This enables the attacker to trick the vault into sending BONK instead of the intended token, causing the recipient to receive significantly less than the expected amount.
Root Cause
When the ledger processes a withdrawal through the SolConnector::withdraw function, the message is sent via LayerZero to the Orderly vault on Solana. In Solana, the message is verified by the endpoint::verify function, and upon successful verification, the PacketVerifiedEvent is emitted, making the message executable. A malicious user listening to PacketVerifiedEvent for the oApp receiver can then deliver the message to solana_vault::lz_receive with the same message as verified in the LayerZero endpoint but with a different deposit_token account:
If the vault authority currently holds other tokens up to the transfer amount, this would allow the attacker to send this other token to the receiver instead of the expected allowed token by setting deposit_token to this alternative token. If the vault authority only has a USDC-associated token account, the attacker could create an associated account for the vault authority, and then send $1 worth of a meme coin like BONK to the associated token account. The amount sent would depend on the set token amount in the constructed withdrawal message in SolConnector:
As of the time of writing this report, 1 USDC is equivalent to 44,898.87640449438 BONK.
Since BONK in Solana has 5 decimal precision and USDC has 6 decimal precision, sending 1 USDC worth of BONK to the vault authority’s associated token account will process up to a 4,489 USDC withdrawal amount. If the receiver is expected to receive 4,000 USDC, they will instead receive 4,000 BONK, which is worth approximately 0.89 USDC.
Note that a BONK associated account can be easily created for the vault authority. The attacker only needs to cover a one-time creation cost:
Leveraging another issue, the account could also be created for the vault authority by depositing BONK to the vault authority via solana_vault::deposit
Internal Preconditions
None
External Preconditions
None
Attack Path
The attacker listens for the PacketVerifiedEvent, which signals that a message has been verified and can be executed.
If the vault authority has an associated token account for BONK, the attacker deposits an amount up to the set withdrawal amount in the message to this account. If there isn’t an account, the attacker creates one for the vault authority, then deposits BONK.
The attacker sends a crafted message to solana_vault::lz_receive, modifying the deposit_token account to use BONK instead of the expected token.
Due to the low value of BONK, the recipient receives far below the expected withdrawal value, resulting in a significant loss.
Impact
The attacker can intercept and manipulate USDC token withdrawals to instead send BONK to the receivers, significantly reducing the expected withdrawal amount's value.
Sunny Syrup Worm
High
Unchecked
deposit_token
Allows Malicious Token Substitution During WithdrawalsSummary
A verified withdrawal message can be maliciously intercepted, replacing the expected token with a different, low-value token (e.g., BONK) by changing the
deposit_token
account. This enables the attacker to trick the vault into sending BONK instead of the intended token, causing the recipient to receive significantly less than the expected amount.Root Cause
When the ledger processes a withdrawal through the
SolConnector::withdraw
function, the message is sent via LayerZero to the Orderly vault on Solana. In Solana, the message is verified by theendpoint::verify
function, and upon successful verification, thePacketVerifiedEvent
is emitted, making the message executable. A malicious user listening toPacketVerifiedEvent
for the oApp receiver can then deliver the message tosolana_vault::lz_receive
with the same message as verified in the LayerZero endpoint but with a differentdeposit_token
account:If the vault authority currently holds other tokens up to the transfer amount, this would allow the attacker to send this other token to the receiver instead of the expected allowed token by setting
deposit_token
to this alternative token. If the vault authority only has a USDC-associated token account, the attacker could create an associated account for the vault authority, and then send $1 worth of a meme coin like BONK to the associated token account. The amount sent would depend on the set token amount in the constructed withdrawal message inSolConnector
:As of the time of writing this report, 1 USDC is equivalent to 44,898.87640449438 BONK.
Since BONK in Solana has 5 decimal precision and USDC has 6 decimal precision, sending 1 USDC worth of BONK to the vault authority’s associated token account will process up to a 4,489 USDC withdrawal amount. If the receiver is expected to receive 4,000 USDC, they will instead receive 4,000 BONK, which is worth approximately 0.89 USDC.
Note that a BONK associated account can be easily created for the vault authority. The attacker only needs to cover a one-time creation cost:
Leveraging another issue, the account could also be created for the vault authority by depositing BONK to the vault authority via
solana_vault::deposit
Internal Preconditions
None
External Preconditions
None
Attack Path
PacketVerifiedEvent
, which signals that a message has been verified and can be executed.solana_vault::lz_receive
, modifying thedeposit_token
account to use BONK instead of the expected token.Impact
The attacker can intercept and manipulate USDC token withdrawals to instead send BONK to the receivers, significantly reducing the expected withdrawal amount's value.
Proof of Concept (PoC)
https://github.com/LayerZero-Labs/LayerZero-v2/blob/7bcfb4d5dac4192570af5e51dbc67413a6116a14/packages/layerzero-v2/solana/programs/programs/endpoint/src/lib.rs#L175-L177
https://solscan.io/token/DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263
Mitigation
Consider ensuring that the
deposit_token
account is indeed the expected allowed token: