Assessment reports>Claim and Rewards Programs>Medium findings>Signature replay in the ,SetAuthority, instruction
Category: Coding Mistakes

Signature replay in the SetAuthority instruction

Medium Impact
Medium Severity
High Likelihood

Description

The process_set_authority_instruction function in the claimable-tokens program contains a signature-replay vulnerability. The SignedSetAuthorityData struct only includes blockhash and instruction data but does not include the specific token_account address that should be modified. This allows an attacker to reuse a valid signature intended for one token account to modify the authority of a different token account, as long as both accounts are derived from the same Ethereum signer address.

// https://github.com/AudiusProject/apps/blob/d8faad177e8fdaa3cf8b79bee452bf00050c7195/solana-programs/claimable-tokens/program/src/state.rs#L24-L29
pub struct SignedSetAuthorityData {
    /// A recent blockhash to prevent replays
    pub blockhash: Hash,
    /// The instruction data as a serialized byte array
    pub instruction: Vec<u8>,
}

The validation logic only verifies that the provided accounts match the derivation from the Ethereum signer; it does not check whether the signature was specifically created for the provided token account.

// https://github.com/AudiusProject/apps/blob/d8faad177e8fdaa3cf8b79bee452bf00050c7195/solana-programs/claimable-tokens/program/src/processor.rs#L419-L432
let signer_address = EthereumAddress::try_from_slice(eth_address_signer)
    .map_err(|_| ClaimableProgramError::SignatureVerificationFailed)?;
let mint = &spl_token::state::Account::unpack(&token_account_info.data.borrow())?.mint;
let pair = find_address_pair(program_id, mint, signer_address)?;
let derived_authority = pair.base.address;
let derived_token_account = pair.derive.address;
if *authority_account_info.key != derived_authority {
    msg!("Authority account mismatch");
    return Err(ClaimableProgramError::SignatureVerificationFailed.into());
}
if *token_account_info.key != derived_token_account {
    msg!("Token account mismatch");
    return Err(ClaimableProgramError::SignatureVerificationFailed.into());
}
// No check that the signature was created for this specific token_account

An attacker who intercepts a legitimate SetAuthority signature can replay it against any other token account derived from the same Ethereum address.

Impact

When a victim transfers authority to an address they do not control (e.g., burning authority by setting to null), an attacker can intercept and replay this signature to unintentionally change the authority of other token accounts derived from the same Ethereum address. While the attacker cannot choose the new authority arbitrarily, they can cause unintended authority changes on the victim's other token accounts.

Recommendations

Modify SignedSetAuthorityData to include the token_account address as part of the signed message. Add validation to verify that the signed token account matches the provided account before executing the authority change.

Remediation

This issue has been acknowledged by Audius, and a fix was implemented in commit 76d5ee21.

Zellic © 2025Back to top ↑