xanoutas/solidity-security-findings huggingface dataset:
Missing Validations in the Tx-Tracker Can Drain The Protocol
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/zeta-chain/node/blob/main/zetaclient/evm_client.go#L930-L961


# Vulnerability details

## Impact

When using the `AddToInTxTracker` message, one can recall a transaction to an observer that this one may have missed. The reason is that downtimes may happen, and so this is a very useful feature to make sure that even if the transaction has been missed because of a potential bug, a consensus failure, or a downtime, the validator will still be able to verify that the transaction is correct and vote on it.

This message is intended to be broadcast to validators to add the transaction to their `InboundTrackers`.

It works by calling the `AddToInTxTracker`, which verifies if the `Creator` of the message has sufficient rights, otherwise if the proof verifies. If any of these is the case, then the transaction is successfully added to the InTxTracker which will be evaluated soon by the `inbound_tracker` in the `ObserveTrackerSuggestions` function.

What this function does is that it loops over every tracker from their respective chain, and verifies that some kind of event happened in order to vote on a transaction. 

The issue is that this verification is too weak for the EVM client.

## Proof of Concept

There is a switch case in the `ObserveTrackerSuggestions` which takes different measures depending on the `CoinType`. We will go through each cases.

### Zeta

In this case, only the `ParseZetaSent` is called which does not care about the address of the emitting contract. To make this function pass, the emitted event should just unpack correctly. Meaning that **any** contract that emits this `ZetaSent` event is going to be verified successfully by the validator. The event can thus be fully arbitrary, including the `zetaValueAndGas` which can be used to steal all the Zeta tokens.

### ERC20

The `Deposited` log is the one that is scanned here, and similarly only the `ParseDeposited` function is called, so the address of the `ERC20Custody` is not checked and we can thus create fake deposits that would credit use real tokens on Zetachain, effectively stealing any ERC20 token from the protocol.

### Gas

In this case, the supposed scenario is that someone sent some funds to the TSS address attached with a message that should broadcasted on Zetachain to some address.

The issue is very similar in this case, the `to` address of the recalled transaction receipt is not checked. Note that a very similar logic is implemented in the `evm_client` .

https://github.com/zeta-chain/node/blob/main/zetaclient/evm_client.go#L930-L961

Here, we can see that the `to` is checked while this is not the case in the `CheckReceiptForCoinTypeGas` function https://github.com/zeta-chain/node/blob/main/zetaclient/inbound_tracker.go#L213-L260, allowing any validator or anyone with a sufficient proof to mint any gas ZRC20 on Zetachain by adding to the in tracker **any** transaction having some value, emptying the funds of the protocol. For instance,
Connector message is not handled on Zetachain
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/zeta-chain/node/blob/main/x/crosschain/keeper/evm_hooks.go#L220


# Vulnerability details

## Impact

When using the Cross-chain messaging functionality, one may pass a message in order for it to be handled on the foreign chain. For the docs, see https://www.zetachain.com/docs/developers/cross-chain-messaging/overview/

This is triggered when emitting the `ZetaSent` on any connector contract registered in the node clients. One of the key features of the connector is to be able to pass cross-chain messages as well as transferring the ZETA token because this is what powers cross-chain dapps. The other possibility is to use the TSS, but we will focus on the connector since this is the one which misses this feature.

In the case of Zetachain → EVM Chain, the `PostTxProcessing` hook calls `ProcessLogs` for every receipt logs.
But when constructing the `VoteOnObserverInboundTx` message, the `Message` field is overwritten https://github.com/zeta-chain/node/blob/main/x/crosschain/keeper/evm_hooks.go#L220
Any data that is passed to this field in the smart contract is thus ignored. Meaning that in the docs, if the `CrossChainMessage.sol` contract is deployed on Zetachain, the `Hello, Cross-Chain World!` message will get swallowed.

![Untitled](https://busy-icecream-3ad.notion.site/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fab97a27b-8903-4eae-8e59-0c541f4685a0%2F6137e1f6-bcb5-4f8b-9a62-d9c806c6fa34%2FUntitled.png?table=block&id=e30ccd4d-b09a-496c-9bae-51a7db35def3&spaceId=ab97a27b-8903-4eae-8e59-0c541f4685a0&width=2000&userId=&cache=v2)

This is an issue because any contract that is highly relying on these cross-chain messages such as a cross chain swap, may have some unexpected behavior if the message is empty, and funds could be lost.

## Proof of Concept

Attack scenario:

1. Deploy a cross-chain app on any EVM chain that is not Zetachain
2. Deploy the messaging functionality contract on Zetachain so that users from Zetachain can interact with the cross-chain app implementation
3. Send a message from the messaging contract, which in turn calls the connector contract
4. Make sure that on the implementation contract, the message is empty

## Tools Used

Manual inspection

## Recommended Mitigation Steps

Pass the message that is read from the calldata of the `send` function when users are interacting with the connector instead of overwriting it to empty.





## Assessed type

Context
RCE & DoS Through the Stdout in ZetaVisor
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/zeta-chain/cosmos-sdk/blob/50787d0ff0aaedc571bc47464be8eb2152a27413/cosmovisor/scanner.go#L20


# Vulnerability details

## Impact

The version of the last version of the zetavisor (**v0.1.5**) is vulnerable to an RCE or Denial Of Service of the nodes that running it.

The vulnerable component ([cosmovisor](https://github.com/advisories/GHSA-23px-mw2p-46qm)) is used to make an upgrade at a certain block for the client of the network (this can be useful in case the clients are vulnerable and the network updates all the clients at once in the same block). 

In the [security advisory](https://github.com/advisories/GHSA-23px-mw2p-46qm), there is an oneliner command to know if the version we are using is vulnerable by checking if the string `NEEDED at` is inside the binary. 

After running it against the Zetavisor we can see that the string is inside so this is using a **vulnerable** version.

![Untitled](https://busy-icecream-3ad.notion.site/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fab97a27b-8903-4eae-8e59-0c541f4685a0%2F10f8610b-0417-47ff-8615-ddd97596a9cc%2FUntitled.png?table=block&id=bdcdbbff-cbb0-43ec-8881-1deace23aa8d&spaceId=ab97a27b-8903-4eae-8e59-0c541f4685a0&width=2000&userId=&cache=v2)

So the `scanner` (`scanner.go`) that is waiting for the upgrade messages has to monitor the `stdout` to be noticed if there is an upgrade to execute. To know if the message is an **upgrade** the scanner will monitor the messages coming with a certain pattern using this regex:

```go
regexp.MustCompile(`UPGRADE "(.*?)" NEEDED at height: (\d+):\s+(\S*)`)
```

[ZetavisorV0.1.5/cosmos-sdk/cosmovisor/scanner.go](https://github.com/zeta-chain/cosmos-sdk/blob/50787d0ff0aaedc571bc47464be8eb2152a27413/cosmovisor/scanner.go#L20)

```go
// Trim off whitespace around the info - match least greedy, grab as much space on both sides
// Defined here: https://github.com/cosmos/cosmos-sdk/blob/release/v0.38.2/x/upgrade/abci.go#L38
//  fmt.Sprintf("UPGRADE \"%s\" NEEDED at %s: %s", plan.Name, plan.DueAt(), plan.Info)
// DueAt defined here: https://github.com/cosmos/cosmos-sdk/blob/release/v0.38.2/x/upgrade/internal/types/plan.go#L73-L78
//
//    if !p.Time.IsZero() {
//      return fmt.Sprintf("time: %s", p.Time.UTC().Format(time.RFC3339))
//    }
//    return fmt.Sprintf("height: %d", p.Height)

// Accommodate both JSON and plain logging formats (JSON: \"plan\", plain: "plan").
var plainUpgradeRegex = regexp.MustCompile(`UPGRADE "(.*?)" NEEDED at height: (\d+):\s+(\S*)`)
var jsonUpgradeRegex = regexp.MustCompile(`UPGRADE (?:\\|)"(.*?)(?:\\|)" NEEDED at height: (\d+):\s+(.*)$`)
```

https://github.com/zeta-chain/cosmos-sdk/blob/50787d0ff0aaedc571bc47464be8eb2152a27413/cosmovisor/scanner.go#L20

As the `scanner` relies on the `stdout` this is dangerous as an attacker can find a way to print a malicious message (thus trigger a malicious upgrade) through the `stdout`.

For doing this attack, an attacker can find a permi
Last Voter in `CreateTSSVoter` Can Set Any `TssPubkey`
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/types/message_tss_voter.go#L51-L54
https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/keeper/msg_tss_voter.go#L90-L105


# Vulnerability details

## Impact

`CreateTSSVoter` function is used to vote on creating a TSS key and recording the information about it. But, due to how the function deals with unvalidated user input, the last voter can easily change the TSS pubkey to an arbitrary value. This results in the compromised or faulty voter being able to steall or burn all TSS funds.

## Proof of Concept

The issue originates from how the function `MsgCreateTSSVoter.Digest` works:

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/types/message_tss_voter.go#L51C1-L54C2
```go
func (msg *MsgCreateTSSVoter) Digest() string {
	// We support only 1 keygen at a particular height
	return fmt.Sprintf("%d-%s", msg.KeyGenZetaHeight, "tss-keygen")
}
```

This "digest" (not really a digest) does not commit to a `TssPubKey`. Its output is then used as an `index` for the ballot:

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/keeper/msg_tss_voter.go#L44
```go
	index := msg.Digest()
```

When the ballot threshold is reached, the check for the ballot finalisation returns `true` and the function continues to set the TSS key and history:

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/keeper/msg_tss_voter.go#L83C1-L87C3
```go
	ballot, isFinalized := k.zetaObserverKeeper.CheckIfFinalizingVote(ctx, ballot)
	if !isFinalized {
		// Return nil here to add vote to ballot and commit state
		return &types.MsgCreateTSSVoterResponse{}, nil
	}
```

When the last voter casts his vote, `msg.TssPubkey` is never checked and is blindly used as the `TssPubkey` for the following critical operations:

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/keeper/msg_tss_voter.go#L90C1-L105C28
```go
	if ballot.BallotStatus != observerTypes.BallotStatus_BallotFinalized_FailureObservation {
		tss := types.TSS{
			TssPubkey:           msg.TssPubkey,
			TssParticipantList:  keygen.GetGranteePubkeys(),
			OperatorAddressList: ballot.VoterList,
			FinalizedZetaHeight: ctx.BlockHeight(),
			KeyGenZetaHeight:    msg.KeyGenZetaHeight,
		}
		// Set TSS history only, current TSS is updated via admin transaction
		// In Case this is the first TSS address update both current and history

		tssList := k.GetAllTSS(ctx)
		if len(tssList) == 0 {
			k.SetTssAndUpdateNonce(ctx, tss)
		}
		k.SetTSSHistory(ctx, tss)
```

As expected, this bypass has serious consequences.

In the first TSS address update, the current TSS and its history are updated immediatelly. The new `TssPubkey` is whatever the last voter sets it to be in the `msg.TssPubkey`.

For subsequent TSS address updates, the current TSS is meant to be finally set by the `Group2` admi
Zero Confirmation `ListUnspentMinMaxAddresses` Causes Desync and Unjust Slashing 
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/bitcoin_client.go#L736-L752


# Vulnerability details

## Impact

The function `FetchUTXOS` in `bitcoin_client.go` is responsible for fetching and ordering all UTXOs deterministically for use by the Bitcoin TSS address. These UTXOs are then selected to construct a new transaction that must be TSS-signed. For our purpose in this context, the main problem is that zero confirmation UTXOs are guaranteed to be ordinarily or maliciously non-deterministic between peers. This will cause desynchronisation amongst the peers, failed TSS signing, and, at the very least, economical damage through randomly wrong slashing of peers that fail the signing. Other, more general, consequences of using zero confirmation UTXOs are also presented in this report.

## Proof of Concept

Inside `FetchUTXOS`, the UTXOs to be spent by the Bitcoin TSS Address are listed and have to be deterministically ordered. When the RPC call `ListUnspentMinMaxAddresses` is performed, a minimum confirmation of **zero** confirmations is passed to the function.

* https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/bitcoin_client.go#L736C1-L752C4
```go
	// fetching all TSS utxos takes 160ms
	utxos, err := ob.rpcClient.ListUnspentMinMaxAddresses(0, maxConfirmations, addresses)
	if err != nil {
		return err
	}
	//ob.logger.WatchUTXOS.Debug().Msgf("btc: fetched %d utxos in confirmation range [0, %d]", len(unspents), maxConfirmations)

	// rigid sort to make utxo list deterministic
	sort.SliceStable(utxos, func(i, j int) bool {
		if utxos[i].Amount == utxos[j].Amount {
			if utxos[i].TxID == utxos[j].TxID {
				return utxos[i].Vout < utxos[j].Vout
			}
			return utxos[i].TxID < utxos[j].TxID
		}
		return utxos[i].Amount < utxos[j].Amount
	})
```

Due to how transaction propagation works in the Bitcoin network, it is certain that different nodes will have different UTXOs in the mempool; even if the same software, configuration, and version are used. In particular when it comes to zero confirmations UTXOs. The subsequent attempt to make the UTXO list deterministic is, thus, futile.

There are critical issues that can be exploited actively or ocasionally happen by the very nature of the network and message propagation.

### TSS Signing Failures

The first and more obvious is the non-determinism between the TSS peers before signing a transaction.

When the Zetacore observer [processes a Bitcoin "`outtx`"](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/zetacore_observer.go#L270), the UTXOs have to be [selected](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/btc_signer.go#L90) from a deterministically ordered UTXOs list ([`ob.utxos`](https://github.com/code-423n4/2023-11-ze
Broken `NonceVoter` Allows Observer to Halt the Chain
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/keeper_chain_nonces.go#L106-L147


# Vulnerability details

## Impact
As it is, `NonceVoter` does not count votes or perform any similar operations. A single observer can use it to change chain nonces. Although there is a comment pointing this function is deprecated, it critically influences the system with its broken implementation. Arbitrarily changing nonces for the connected chains can be used to completely break the functioning of the chain and cause loss of funds.

## Proof of Concept

This function is restricted to validator observers (`k.zetaObserverKeeper.IsAuthorized(ctx, msg.Creator, chain)`):

* https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/keeper_chain_nonces.go#L106C1-L115C3
```go
func (k msgServer) NonceVoter(goCtx context.Context, msg *types.MsgNonceVoter) (*types.MsgNonceVoterResponse, error) {
	ctx := sdk.UnwrapSDKContext(goCtx)
	chain := k.zetaObserverKeeper.GetParams(ctx).GetChainFromChainID(msg.ChainId)
	if chain == nil {
		return nil, observertypes.ErrSupportedChains
	}

	if ok := k.zetaObserverKeeper.IsAuthorized(ctx, msg.Creator, chain); !ok {
		return nil, observertypes.ErrNotAuthorizedPolicy
	}
	// ... SNIP ...
```

After this check, the entire voting logic is essentially ignored, with a relevant part only drafted and commented, and the chain nonce is simply set:

* https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/keeper_chain_nonces.go#L145C1-L146C44
```go
	k.SetChainNonces(ctx, chainNonce)
	return &types.MsgNonceVoterResponse{}, nil
```

This can be easily tested inside one of the hosts in the smoke-tests:

```bash
# cd into repos/node
$ make start-smoketest
$ docker exec -it zetacore0 bash
# Inside zetacore0
# The operator key may be acquired with the command `zetacored keys list`
$ zetacored tx crosschain nonce-voter 18444 1337 --from ${operator_key} --fees "300azeta"
# Confirm the prompt with "y"
$ zetacored q crosschain list-chain-nonces
# OUTPUT:
ChainNonces:
- chain_id: "18444"
  creator: ""
  finalizedHeight: "57"
  index: btc_regtest
  nonce: "1337" # <--- Chain nonce
  signers:
  - zeta1t4n5yj0u3wjkjwum5exhgh5834gnylgs3apmw5
- chain_id: "1337"
  creator: ""
  finalizedHeight: "57"
  index: goerli_localnet
  nonce: "0"
  signers: []
- chain_id: "101"
  creator: ""
  finalizedHeight: "57"
  index: zeta_mainnet
  nonce: "0"
  signers: []
pagination:
  next_key: null
  total: "0"
```

After the above commands, you can also use the command `zetacored q crosschain list-chain-nonces` inside `zetacored1` to check the `1337` nonce has been set.

### `p.NonceHigh != int64(nonce.Nonce)`

One simple way to halt critical operations of the chain and cause funds to be stuck is to make the function `UpdateNonce` in `cctx_utils.go` fai
`CreateTSSVoter` Ballots DoS
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/types/message_tss_voter.go#L51-L54


# Vulnerability details

## Impact

`CreateTSSVoter` function is used to vote on creating a TSS key and recording the information about it. Only one `keygen` can be created at a particular height, so `CreateTSSVoter` tries to adhere to this logic by limiting the ballot creation to one per height. With this limitation, a faulty or compromised observer can DoS the ballot creation mechanism by spamming multiple ballots at different future heights to cause a DoS on TSS creation voting.

## Proof of Concept

The limit of one ballot per height is enforced by committing a `KeyGenZetaHeight` to the ballot's index with the `Digest` function:

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/types/message_tss_voter.go#L51C1-L54C2
```go
func (msg *MsgCreateTSSVoter) Digest() string {
	// We support only 1 keygen at a particular height
	return fmt.Sprintf("%d-%s", msg.KeyGenZetaHeight, "tss-keygen")
}
```

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/keeper/msg_tss_voter.go#L44
```go
	index := msg.Digest()
```

A faulty or compromised observer can use this limitation to use the `CreateTSSVoter` message to create spam ballots at different future heights in order to DoS the voting system by forbidding a ballot to be set for the targeted heights.

Eventually, when sufficiently slashed by the reward distribution algorithm, the damage is already done and no more ballots can be created for the spammed heights. Depending on the real costs for a ZetaChain mainnet, this attack can render `CreateTSSVoter` useless for enough time to cause financial damage. Specially, if the faulty or compromised observer waits for a time when a quick or emergency TSS creation is necessary to perform the attack.

## Tools Used

Manual: code editor.

## Recommended Mitigation Steps

Allow `CreateTSSVoter` to create more than one ballot per height and let the slashing algorithm deal with ballot spammers. If necessary, adjust the slashing algorithm to account for that. Even though both present different root causes and impacts, the solution provided in another finding in `CreateTSSVoter` is also a potential solution to this one. We could then just commit the ballot index to the `msg.TssPubkey`:

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/types/message_tss_voter.go#L51C1-L54C2
```diff
func (msg *MsgCreateTSSVoter) Digest() string {
-	// We support only 1 keygen at a particular height
-	return fmt.Sprintf("%d-%s", msg.KeyGenZetaHeight, "tss-keygen")
+	return fmt.Sprintf("%d-%s-%s", msg.KeyGenZetaHeight, msg.TssPubkey, "tss-keygen")
}
```



## Assessed type

DoS
Missing access modifier
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerPancakeV3.strategy.sol#L103


# Vulnerability details

The `getZetaFromEth`, `getZetaFromToken`, `getEthFromZeta`, and `getTokenFromZeta` functions lack an access modifier and can be called by anyone. This could lead to issues with accounting or abuse.

**Fix:** Add an `onlyOwner` modifier:

```solidity
function getZetaFromEth(
  //...
) external payable override onlyOwner returns (uint256) {
  //...
}
```



## Assessed type

Access Control