Skip to content
Quantix Security Labs

Account Cosplay

Show how a caller who is not the vault authority can pass the authorisation check and withdraw from the vault, then make the account constraints do the work.
IntermediateRust (Anchor) · SolanaAccount validation30–45 minutes

Briefing

On Solana every account passed to a program is chosen by the caller. A program must check which program owns an account and how it relates to the others before trusting its data.

lib.rsrust
// Quantix Security Research Network — training challenge.// Intentionally vulnerable. Written for education; do not deploy.use anchor_lang::prelude::*;use anchor_spl::token::{self, Token, TokenAccount, Transfer}; declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); #[program]pub mod training_vault {    use super::*;     pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {        let data = ctx.accounts.config.try_borrow_data()?;        let config = VaultConfig::try_deserialize(&mut &data[..])?;        require_keys_eq!(config.authority, ctx.accounts.authority.key(), VaultError::Unauthorized);         let seeds: &[&[u8]] = &[b"vault", &[ctx.bumps.vault_authority]];        token::transfer(            CpiContext::new_with_signer(                ctx.accounts.token_program.to_account_info(),                Transfer {                    from: ctx.accounts.vault_token.to_account_info(),                    to: ctx.accounts.destination.to_account_info(),                    authority: ctx.accounts.vault_authority.to_account_info(),                },                &[seeds],            ),            amount,        )    }} #[account]pub struct VaultConfig {    pub authority: Pubkey,    pub vault_token: Pubkey,} #[derive(Accounts)]pub struct Withdraw<'info> {    /// CHECK: deserialised manually in the handler    pub config: AccountInfo<'info>,    pub authority: Signer<'info>,    #[account(mut)]    pub vault_token: Account<'info, TokenAccount>,    #[account(mut)]    pub destination: Account<'info, TokenAccount>,    /// CHECK: PDA that owns the vault token account    #[account(seeds = [b"vault"], bump)]    pub vault_authority: AccountInfo<'info>,    pub token_program: Program<'info, Token>,} #[error_code]pub enum VaultError {    #[msg("Unauthorized")]    Unauthorized,}

Hints

Reveal one at a time. Each moves a step closer to the answer.

  1. H1

    Hidden

  2. H2

    Hidden

  3. H3

    Hidden

Solution

Try writing your own explanation and fix before opening it.

References