Token Account Spoofing
Detects token account spoofing and aliasing vulnerabilities where fake token accounts can be substituted.
Token Account Spoofing
Overview
Remediation Guide: How to Fix Token Account Spoofing
The token account spoofing detector identifies Solana programs that use token accounts in operations without verifying their mint, owner, or key. Attackers can substitute fake token accounts with wrong mints or attacker-controlled owners to steal funds. The detector also identifies account aliasing where the same account is used as both source and destination, breaking accounting invariants.
Why This Is an Issue
Solana programs receive accounts as opaque AccountInfo structs. Without explicit validation, an attacker can pass any account in any position:
- Wrong mint spoofing: Pass a token account for a worthless mint instead of the expected valuable mint, receiving real tokens in exchange for fakes
- Owner spoofing: Pass an attacker-owned token account as the “user’s” account, redirecting funds
- Account aliasing: Pass the same account as both source and destination in a swap, breaking balance invariants and potentially duplicating funds
These vulnerabilities have been exploited in DeFi protocols causing significant losses.
CWE mapping: CWE-345 (Insufficient Verification of Data Authenticity), CWE-662 (Improper Synchronization).
How to Resolve
Native Rust
pub fn swap(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let token_a = &accounts[0];
let token_b = &accounts[1];
// Verify mints match expected tokens
let data_a = Account::unpack(&token_a.data.borrow())?;
let data_b = Account::unpack(&token_b.data.borrow())?;
if data_a.mint != EXPECTED_MINT_A { return Err(ProgramError::InvalidAccountData); }
if data_b.mint != EXPECTED_MINT_B { return Err(ProgramError::InvalidAccountData); }
// Verify owner
if data_a.owner != *user.key { return Err(ProgramError::InvalidAccountData); }
// Prevent aliasing
if token_a.key == token_b.key { return Err(ProgramError::InvalidArgument); }
// Safe to proceed
Ok(())
}
Anchor
#[derive(Accounts)]
pub struct Swap<'info> {
#[account(mut, token::mint = mint_a, token::authority = user)]
pub token_a: Account<'info, TokenAccount>,
#[account(mut, token::mint = mint_b,
constraint = token_a.key() != token_b.key() @ ErrorCode::AccountAliasing)]
pub token_b: Account<'info, TokenAccount>,
pub user: Signer<'info>,
}
Examples
Vulnerable Code
pub fn swap(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let token_a = &accounts[0]; // No mint check!
let token_b = &accounts[1]; // No mint check!
// No aliasing check!
// Attacker passes fake token account with worthless mint
transfer_tokens(token_a, token_b, amount)?;
Ok(())
}
Fixed Code
pub fn swap(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let token_a = &accounts[0];
let token_b = &accounts[1];
let user = &accounts[2];
let data_a = Account::unpack(&token_a.data.borrow())?;
let data_b = Account::unpack(&token_b.data.borrow())?;
if data_a.mint != MINT_A || data_b.mint != MINT_B {
return Err(ProgramError::InvalidAccountData);
}
if data_a.owner != *user.key { return Err(ProgramError::IllegalOwner); }
if token_a.key == token_b.key { return Err(ProgramError::InvalidArgument); }
transfer_tokens(token_a, token_b, amount)?;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "token-account-spoofing",
"severity": "high",
"confidence": 0.82,
"description": "Token account used in token operation without proper validation. Missing: mint verification, owner verification.",
"location": { "function": "swap", "offset": 1 }
}
Detection Methodology
- Validation collection: Tracks
CheckOwner,CheckKeystatements and key-to-constant comparisons as evidence of mint, owner, and key validation. - Token operation analysis: Identifies CPI calls that constitute token operations and extracts account variables used.
- Validation gap analysis: Reports accounts used in token operations that lack mint, owner, or key verification.
- Aliasing detection: Identifies accounts used multiple times in mutable contexts within the same function, flagging potential aliasing.
Limitations
- Mint validation via field access and comparison is heuristic-based; some validation patterns may not be recognized.
- Anchor’s typed
Account<'info, TokenAccount>validates discriminator and owner at deserialization, which reduces spoofing risk but may not be detected. - Cross-function validation (e.g., validation in a caller) is not tracked.
Related Detectors
- Account Alias Attack — detects account aliasing in non-token contexts
- SPL Token ATA Validation — detects improper ATA derivation
- Duplicate Mutable Accounts — detects duplicate mutable account usage