Disabling outbound transactions is ineffective and allows for Zeta token theft
criticalLines of code
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_signer.go#L452 https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_signer.go#L530-L544
Vulnerability details
Impact
Outbound EVM transactions can not be disabled and will be wrongly sent out, either stealing Zeta tokens or manifesting in a loss for users who attempt to withdraw gas or ERC-20 tokens.
Proof of Concept
In lines 530-544 of the TryProcessOutTx function, called by the observer's EVM signer to send an outbound cctx transaction to the receiver chain, the SignOutboundTx function is invoked to sign the transaction tx.
This transaction has the Zeta connector contract as the to address and the ABI encoded onReceive function call as the calldata. As a result, the connector contract, e.g., the ZetaConnectorEth contract, has the onReceive function called and subsequently transfers Zeta tokens to the destinationAddress.
Now let's re-visit a critical part of the TryProcessOutTx function, specifically, lines 439-544:
go439: if send.GetCurrentOutTxParam().CoinType == common.CoinType_Cmd { // admin command ... // [...] 452: } else if send.InboundTxParams.SenderChainId == common.ZetaChain().ChainId && send.CctxStatus.Status == types.CctxStatus_PendingOutbound && flags.IsOutboundEnabled { 453: if send.GetCurrentOutTxParam().CoinType == common.CoinType_Gas { ... // [...] 462: } 463: if send.GetCurrentOutTxParam().CoinType == common.CoinType_ERC20 { ... // [...] 475: } 476: if send.GetCurrentOutTxParam().CoinType == common.CoinType_Zeta { ... // [...] 490: } 491: } else if send.CctxStatus.Status == types.CctxStatus_PendingRevert && send.OutboundTxParams[0].ReceiverChainId == common.ZetaChain().ChainId { ... // [...] 515: } else if send.CctxStatus.Status == types.CctxStatus_PendingRevert { ... // [...] 530: } else if send.CctxStatus.Status == types.CctxStatus_PendingOutbound { ... // [...] 532: tx, err = signer.SignOutboundTx( 533: ethcommon.HexToAddress(send.InboundTxParams.Sender), 534: big.NewInt(send.InboundTxParams.SenderChainId), 535: to, 536: send.GetCurrentOutTxParam().Amount.BigInt(), 537: gasLimit, 538: message, 539: sendhash, 540: send.GetCurrentOutTxParam().OutboundTxTssNonce, 541: gasprice, 542: height, 543: ) 544: }
Here, the type of transaction signature is determined, based on the coin type, the originator (SenderChainId), and the status of the cctx.
The possible states are as follows (the order is important):
- Line 439: Admin command
- Line 452: Pending outbound cctx, originating from ZetaChain. Additionally, in lines
453,463, and476, the coin type is checked to determine the specific type of transaction. - Line 491: Revert a failed cctx that was originally sent to ZetaChain
- Line 515: Revert a failed cctx that was originally sent to an external chain
- Line 530: Pending outbound cctx. No matter the coin type, it will always result in a Zeta token transfer
In the second state, in line 452, it is also checked if outbound transactions are generally enabled (flags.IsOutboundEnabled) and if they are disabled, this else if block is skipped while the if in line 530 evaluates to true.
However, this is problematic in two ways:
- Even though outbound transactions are disabled, cctxs are still sent out.
- No matter the coin type of the pending outbound cctx, it will be signed via
SignOutboundTxand resulting in a Zeta token transfer
For the second case, consider a pending outbound cctx with the coin type common.CoinType_ERC20 where the ERC-20 asset is worth less than the Zeta token (please note that ERC-20 tokens are restricted by a whitelist).
As a result of this issue, the recipient of the cctx receives Zeta tokens instead of the lower-valued ERC-20 tokens, effectively stealing Zeta tokens for profit. Moreover, if users attempt to withdraw gas or ERC-20 tokens that have a higher value than the Zeta token, users will receive a total value Zeta tokens less than their original gas/ERC-20 tokens, resulting in a loss.
Tools Used
Manual review
Recommended mitigation steps
Consider refactoring the control flow by first checking if outbound transactions are enabled (the CoinType_Cmd cctx can be exempted from this check), and only then continue with the other checks.
Assessed type
Invalid Validation
