Disclaimer: This article discusses a vulnerability disclosure related to Axelar Network. Marco Hextor is publishing this information as part of his company’s security research work and is not affiliated with, endorsed by, or sponsored by Axelar Network. This research was conducted independently and facilitated through the Immunefi bug bounty platform. A bug bounty reward was received for the responsible disclosure of this vulnerability.

Target: Axelar Network
Reported on: 7 May 2024
Status: Resolved / Paid
Reward: $50,000 (USDC)

Important Update (March 20, 2025): This article provides a vulnerability disclosure, describing a past exploit path within the Axelar Network. Axelar Network has since confirmed that the Chain Maintainer auto-deregistration mechanism, which was central to triggering this vulnerability, has been disabled via governance proposal 256 (https://axelarscan.io/proposal/256). This vulnerability is no longer exploitable since then. The article below is a retrospective analysis of the vulnerability and the disclosure process.

Enter Axelar

Axelar relies on a set of validators that participate in block creation, multi-party cryptography protocols, and voting. All of this happens in the Axelar chain (axelar-core), an application built on Cosmos SDK.

The key role to understand this vulnerability is the validators’ responsibility as “Chain Maintainers”: they vote on External Chain Events.

Besides participating in Tendermint consensus, validators register as maintainers for the chains they choose to support, voting regularly on these chains’ External Chain Events. Axelar naturally enforces specific rules to guarantee validator availability and integrity:

  • Validators who consistently miss votes beyond a defined threshold within a specific block window are penalised by losing rewards and deregistered for safety.
  • A chain requires a minimum validator quorum (0.6 from the total consensus weight of all validators in the system) to enable cross-chain messages from a given EVM chain; below this threshold, cross-chain operations from that chain are halted.

Validators also run a companion off-chain software called vald to timely cast their votes on External Chain Events.

For simplicity, we’ll focus specifically on the transaction logs emitted by the AxelarGateway contract on EVM chains after executing callContract successfully:

    event ContractCall(
        address indexed sender,
        string destinationChain,
        string destinationContractAddress,
        bytes32 indexed payloadHash,
        bytes payload
    );

// Source: https://github.com/axelarnetwork/axelar-cgp-solidity/blob/83012e6f6fb721d7ffaaec9426fdac3c02fced2e/contracts/interfaces/IAxelarGateway.sol#L29

When users invoke callContract, this triggers the emission of an event. Validators subsequently vote on the polls generated after the Axelar chain receives the relevant transaction ID(s) through the ConfirmGatewayTxs message:

  1. EVM Chain:
    1. User calls callContract(...) on AxelarGateway contract
    2. AxelarGateway emits the ContractCall event
  2. Axelar Chain (Cosmos):
    1. ConfirmGatewayTxs message is processed with the relevant txids
    2. Polls are generated based on these txids
    3. vald, constantly running, is used by Validators (as Chain Maintainers) to vote on the polls automatically via Tendermint’s RPC

With this simplified background, let’s explore the vulnerability.

The Primitive

I discovered that validators can be forced to predictably skip voting on transactions containing an excessive number of logs due to a default Tendermint configuration limiting the RPC request body size to approximately 1 MB (max_body_bytes). Typically, skipping votes is safer than potentially casting incorrect votes.

# Maximum size of request body, in bytes
max_body_bytes = 1000000

# Source: https://github.com/axelarnetwork/axelarate-community/blob/95ac438ea31e954acf0bdd556b43e3933be994ab/configuration/config.toml#L146

This configuration is the seldom changed default on Tendermint and the one validators end up with after following Axelar’s official setup instructions. Notably, as this limit isn’t enforced by consensus, validators could silently diverge in voting behaviour if their settings differ, which may lead to dangerous behaviour – an intriguing point, though a separate matter.

The critical point is: validators reliably skip voting whenever the logs data is large enough to make the subsequent RPC request to Axelar Chain exceed this 1 MB limit. This undocumented limit could itself be considered a vulnerability in certain scenarios, but it wouldn’t be sufficient to justify a Critical/High severity impact reward on its own. Instead, it serves as the trigger mechanism for the core vulnerability.

The Vulnerability

The primitive alone is seemingly harmless. That’s because the concerning vulnerability I encountered is that Axelar lacks a minimum voting quorum requirement before penalising and deregistering validators for missed votes. This means that if we can consistently force Chain Maintainers to miss voting above the threshold, we can have them deregistered as Chain Maintainers and their rewards forfeited. And, given this affects virtually all Chain Maintainers in the network, we can effectively halt Axelar’s EVM cross-chain operations, affecting millions in locked cross-chain value.

Missing votes are marked and penalised:

[... SNIP ...]
// Penalize voters who failed to vote
for _, voter := range poll.GetVoters() {
    hasVoted := poll.HasVoted(voter)
    if maintainerState, ok := v.nexus.GetChainMaintainerState(ctx, chain, voter); ok {
        maintainerState.MarkMissingVote(!hasVoted)
        funcs.MustNoErr(v.nexus.SetChainMaintainerState(ctx, maintainerState))

        v.keeper.Logger(ctx).Debug(fmt.Sprintf("marked voter %s behaviour", voter.String()),
            "voter", voter.String(),
            "missing_vote", !hasVoted,
            "poll", poll.GetID().String(),
        )
    }

    if !hasVoted {
        rewardPool.ClearRewards(voter)
        v.keeper.Logger(ctx).Debug(fmt.Sprintf("penalized voter %s due to timeout", voter.String()),
            "voter", voter.String(),
            "poll", poll.GetID().String())
    }
}
[... SNIP ...]

// Source: https://github.com/axelarnetwork/axelar-core/blob/c7afbd0703a1f9ee1af415490ba2e931fc2bc1f7/x/evm/keeper/vote_handler.go#L64C1-L84C3

Missing vote threshold for Chain Maintainer removal:

ChainMaintainerMissingVoteThreshold:   utils.NewThreshold(20, 100),
[... SNIP ...]
ChainMaintainerCheckWindow:            500,

// Source: https://github.com/axelarnetwork/axelar-core/blob/c7afbd0703a1f9ee1af415490ba2e931fc2bc1f7/x/nexus/types/params.go#L38C2-L40C46

This default had remained unchanged and meant that validators could miss up to 100 polls within the 500-poll check window without being removed. When they missed the 101st poll within the check window, they were removed as Chain Maintainers.

Chain Maintainer removal:

[... SNIP ...]
if hasProxyActive &&
    utils.NewThreshold(int64(missingVoteCount), int64(window)).LTE(params.ChainMaintainerMissingVoteThreshold) &&
    utils.NewThreshold(int64(incorrectVoteCount), int64(window)).LTE(params.ChainMaintainerIncorrectVoteThreshold) {
    continue
}

rewardPool.ClearRewards(maintainerState.GetAddress())
if err := n.RemoveChainMaintainer(ctx, chain, maintainerState.GetAddress()); err != nil {
    return err
}
[... SNIP ...]

// Source: https://github.com/axelarnetwork/axelar-core/blob/c7afbd0703a1f9ee1af415490ba2e931fc2bc1f7/x/nexus/abci.go#L47C1-L56C5

This vulnerability could potentially be targeted in many different ways, but I needed to use the primitive to prove it’s exploitable. Otherwise, it wouldn’t qualify as a valid bounty submission.

The Exploit

I’ll demonstrate just the exploit path I chose for the PoC. Obviously, there are other approaches, such as using different event types or creating a different combination of logs per (up to 10) txids. The only requirement is that the resulting data payload for the vald RPC call must be above the set RPC request limit.

So, with the vulnerability and our primitive as a way to trigger the vulnerability, this is the attack sequence:

  1. EVM chain: Attacker creates 2 “malicious” transactions that make AxelarGateway store 2000 ContractCall logs each
  2. Axelar Chain: Attacker sends the ConfirmGatewayTxs message on Axelar Chain with the malicious txids as parameters; this triggers the initialisation of two polls to be voted by Chain Maintainers
  3. Off-chain: vald detects that the polls have been initialised on Axelar Chain for two txids, fetches the stored logs data from their EVM RPC endpoint, attempts to vote… Fails due to the RPC request limit (max_body_bytes)
  4. Attack completion: Attacker repeats step 2, which triggers step 3, enough times within a block interval window (missing vote threshold) to trigger massive Chain Maintainer deregistration for the targeted EVM chain due to missing votes

For the malicious logs preparation in the PoC, I used a Forge script that had to be called for each transaction: https://gist.github.com/marcohextor/d1df2d2ac4e7226895ac973d294fc687

(Yeah, this can be optimised. This is acceptable for this PoC, and most costs come from storing the logs anyway)

Cost Analysis

This was valid approximately at the time of reporting and may vary in either direction. Values have been deliberately overestimated to provide a substantial safety margin in the analysis.

  • One-time setup cost for log creation across the most active EVM chains on Axelar at the time (Ethereum, BSC, Polygon PoS, Avalanche, Arbitrum, and Base): 4000 USD + (200 USD * 5) = 5000 USD (Fixed cost)
  • ~66 USD to drop Chain Maintainers on Axelar Chain for all EVM chains (Moving cost)
  • Total initial cost (over-inflated): 5066 USD
  • From now on a moving cost of 11 USD on Axelar Chain fees to drop the Chain Maintainers for each chain that reaches the threshold to resume operations, indefinitely. Anyone can do it with the same Gateway transaction IDs repeatedly and “permissionlessly” without having to recreate the costlier part of the attack.

$5066 to halt cross-chain operations from the six most active EVM chains on Axelar Chain, until the minimum quorum of re-registered Chain Maintainers is reached, only for them to be cheaply dropped again ($11 per chain) by anyone.

Impact

The main direct impact is Vulnerabilities related to validator voting manipulation on external chain events (Critical), an impact that existed at their BBP at the time.

This attack allows an attacker to systematically force the deregistration of all Chain Maintainers across multiple EVM chains, causing the system to fall below the required 60% validator quorum threshold. Without this minimum quorum, all cross-chain operations between affected chains halt. Even when validators eventually manually re-register themselves as Chain Maintainers, the attacker can cheaply trigger their deregistration again. This creates a complete blockage of cross-chain transactions that can be sustained continuously, effectively rendering the cross-chain functionality inoperable for the targeted EVM chains until emergency actions are implemented.

Severity Classification and Resolution

Initial Response: The project classified the report as “Medium” within 32 minutes, dismissing the chain halt vector. Then offered me $5,000.

Escalation: Mediation was requested. Immunefi confirmed the “Critical” severity based on the PoC.

Negotiation: The project persisted in disputing the severity, eventually proposing a “compromise” of $20,000 while maintaining a Medium severity. This was rejected as it violated the Bug Bounty Program (BBP) terms.

Final outcome: After 5 months and 4 rounds of mediation, the project accepted the Critical classification and awarded the full $50,000 bounty.