Skip to content

Latest commit

 

History

History

README.md

Missing Signer Authorization - Security Example

🎯 Vulnerability Overview

Severity: 🔴 CRITICAL

This example demonstrates one of the most dangerous and common vulnerabilities in Solana programs: Missing Signer Authorization.

The Problem

When a program needs to verify that a specific account authorized an action, it must check that the account actually signed the transaction. Simply checking the account's public key is not enough!

Real-World Impact

This vulnerability has led to multiple exploits in production Solana programs, resulting in:

  • Complete drainage of protocol funds
  • Unauthorized access to privileged operations
  • Loss of user assets

📚 What You'll Learn

  1. ✅ Why checking public keys alone is insufficient
  2. ✅ The difference between AccountInfo and Signer types
  3. ✅ How attackers exploit missing signer checks
  4. ✅ How to properly validate transaction signers in Anchor

🏗️ Program Structure

This program contains a simple vault system with three instructions:

  1. initialize - Creates a vault with an authority
  2. deposit - Deposits SOL into the vault (anyone can deposit)
  3. withdraw_vulnerable ⚠️ - Vulnerable withdrawal (missing signer check)
  4. withdraw_secure ✅ - Secure withdrawal (proper signer check)

⚠️ The Vulnerability: Missing Signer Check

Vulnerable Code

Located in: src/instructions/withdraw_vulnerable.rs

#[derive(Accounts)]
pub struct WithdrawVulnerable<'info> {
    #[account(mut)]
    pub vault: Account<'info, Vault>,

    /// ⚠️ CRITICAL VULNERABILITY: Using AccountInfo instead of Signer
    pub authority: AccountInfo<'info>,  // ❌ WRONG!

    #[account(mut)]
    pub recipient: SystemAccount<'info>,
}

pub fn handler(ctx: Context<WithdrawVulnerable>, amount: u64) -> Result<()> {
    // ⚠️ This check is INSUFFICIENT!
    // We only verify the PUBLIC KEY matches, not that they SIGNED!
    require!(
        ctx.accounts.authority.key() == vault.authority,
        ErrorCode::Unauthorized
    );

    // Transfer funds...
}

Why This Is Dangerous

The code checks authority.key() == vault.authority, which only verifies:

  • ✅ The public key matches

But it does NOT verify:

  • ❌ That the authority signed the transaction
  • ❌ That the caller has the private key
  • ❌ That this is an authorized action

The Attack

  1. Alice creates a vault and deposits 10 SOL
  2. Bob (attacker) calls withdraw_vulnerable:
    • Passes Alice's public key as the authority (doesn't need her private key!)
    • Passes his own wallet as the recipient
  3. The check authority.key() == vault.authority passes
  4. Bob steals all 10 SOL! 💸

Attack Code Example

// Attacker doesn't need the victim's private key!
await program.methods
  .withdrawVulnerable(new BN(10_000_000_000))
  .accounts({
    vault: victimVaultPDA,
    authority: victimPublicKey,  // Just the PUBLIC KEY!
    recipient: attackerWallet.publicKey,  // Attacker's wallet
  })
  .rpc();

// 💰 Funds stolen!

✅ The Fix: Proper Signer Check

Located in: src/instructions/withdraw_secure.rs

#[derive(Accounts)]
pub struct WithdrawSecure<'info> {
    #[account(mut)]
    pub vault: Account<'info, Vault>,

    /// ✅ SECURE: Using Signer type
    #[account(
        constraint = authority.key() == vault.authority @ ErrorCode::Unauthorized
    )]
    pub authority: Signer<'info>,  // ✅ CORRECT!

    #[account(mut)]
    pub recipient: SystemAccount<'info>,
}

What Changed?

One simple change fixes the vulnerability:

- pub authority: AccountInfo<'info>,  // ❌ Vulnerable
+ pub authority: Signer<'info>,       // ✅ Secure

How It Prevents The Attack

When using Signer<'info>:

  1. Anchor automatically verifies the account signed the transaction
  2. If the signature is missing, the transaction fails immediately
  3. Attackers cannot pass a public key without the private key
  4. Funds are safe!

Alternative Manual Check

If you must use AccountInfo, you can manually verify:

pub authority: AccountInfo<'info>,

// In handler:
require!(
    authority.is_signer,
    ErrorCode::MissingRequiredSignature
);

But using Signer<'info> is CLEANER and SAFER!

🔑 Key Takeaways

❌ DON'T

  • Use AccountInfo for accounts that must sign
  • Only check public keys for authorization
  • Assume accounts are signers without verification

✅ DO

  • Use Signer<'info> for accounts that must sign transactions
  • Let Anchor handle signer verification automatically
  • Always verify both identity AND signature

📖 Related Concepts

  • Public Key Cryptography: Public keys are... public! Anyone can know them.
  • Digital Signatures: Only the private key holder can create valid signatures.
  • Transaction Signers: Solana requires all signers to be listed in the transaction.

🚀 Building This Example

# Navigate to the program directory
cd 0-missing-signer-check

# Build the program
anchor build

📚 Additional Resources

⚖️ License

This code is for educational purposes only. The vulnerable code is intentionally insecure to demonstrate security concepts.


Remember: In production, ALWAYS use Signer<'info> for accounts that must authorize actions!