Solana漏洞扫描器Skill solana-vulnerability-scanner

这个技能用于自动扫描Solana区块链程序(包括原生Rust和Anchor框架)的6个关键安全漏洞,如任意跨程序调用(CPI)、程序派生地址(PDA)验证不当、缺失签名和所有权检查等。适用于智能合约安全审计、漏洞挖掘、预发布安全评估等场景。关键词:Solana漏洞扫描、智能合约安全、Anchor程序审计、CPI安全、PDA验证、安全漏洞检测、区块链安全。

智能合约 0 次安装 0 次浏览 更新于 3/14/2026

name: solana-vulnerability-scanner description: 扫描Solana程序中的6个关键漏洞,包括任意CPI、不当的PDA验证、缺失签名/所有权检查和系统变量欺骗。在审计Solana/Anchor程序时使用。

Solana漏洞扫描器

1. 目的

系统扫描Solana程序(原生和Anchor框架)中与跨程序调用、账户验证和程序派生地址相关的平台特定安全漏洞。这个技能编码了6个Solana账户模型独有的关键漏洞模式。

2. 使用时机

  • 审计Solana程序(原生Rust或Anchor)
  • 审查跨程序调用(CPI)逻辑
  • 验证程序派生地址(PDA)实现
  • 协议预发布安全评估
  • 审查账户验证模式
  • 评估指令内省逻辑

3. 平台检测

文件扩展名和指示器

  • Rust文件: .rs

语言/框架标记

// 原生Solana程序指示器
use solana_program::{
    account_info::AccountInfo,
    entrypoint,
    entrypoint::ProgramResult,
    pubkey::Pubkey,
    program::invoke,
    program::invoke_signed,
};

entrypoint!(process_instruction);

// Anchor框架指示器
use anchor_lang::prelude::*;

#[program]
pub mod my_program {
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        // 程序逻辑
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub authority: Signer<'info>,
}

// 常见模式
AccountInfo, Pubkey
invoke(), invoke_signed()
Signer<'info>, Account<'info>
#[account(...)] 带约束
seeds, bump

项目结构

  • programs/*/src/lib.rs - 程序实现
  • Anchor.toml - Anchor配置
  • Cargo.toml 包含 solana-programanchor-lang
  • tests/ - 程序测试

工具支持

  • Trail of Bits Solana Lints: Solana的Rust代码检查器
  • 安装: 添加到Cargo.toml
  • anchor test: 内置测试框架
  • Solana Test Validator: 本地测试环境

4. 这个技能如何工作

当调用时,我将:

  1. 搜索你的代码库 寻找Solana/Anchor程序
  2. 分析每个程序 针对6个漏洞模式
  3. 报告发现 带有文件引用和严重性
  4. 提供修复 针对每个识别出的问题
  5. 检查账户验证 和CPI安全

5. 漏洞模式(6个模式)

我检查6个Solana独有的关键漏洞模式。有关详细的检测模式、代码示例、缓解措施和测试策略,请参阅VULNERABILITY_PATTERNS.md

模式摘要:

  1. 任意CPI ⚠️ 关键 - CPI调用中用户控制的程序ID
  2. 不当的PDA验证 ⚠️ 关键 - 使用create_program_address而不带规范bump
  3. 缺失所有权检查 ⚠️ 高 - 反序列化账户而不进行所有者验证
  4. 缺失签名检查 ⚠️ 关键 - 权限操作而不进行is_signer检查
  5. 系统变量账户检查 ⚠️ 高 - 欺骗的系统变量账户(Solana 1.8.1之前)
  6. 不当的指令内省 ⚠️ 中 - 允许重用的绝对索引

有关完整的漏洞模式及代码示例,请参阅VULNERABILITY_PATTERNS.md

5. 扫描工作流

步骤1:平台识别

  1. 验证Solana程序(原生或Anchor)
  2. 检查Solana版本(1.8.1+ 用于系统变量安全)
  3. 定位程序源(programs/*/src/lib.rs
  4. 识别框架(原生 vs Anchor)

步骤2:CPI安全审查

# 查找所有CPI调用
rg "invoke\(|invoke_signed\(" programs/

# 检查每个调用前的程序ID验证
# 应该在invoke前看到程序ID检查

对于每个CPI:

  • [ ] 调用前验证程序ID
  • [ ] 不能传递用户控制的程序账户
  • [ ] Anchor: 使用 Program<'info, T> 类型

步骤3:PDA验证检查

# 查找PDA使用
rg "find_program_address|create_program_address" programs/
rg "seeds.*bump" programs/

# Anchor: 检查seeds约束
rg "#\[account.*seeds" programs/

对于每个PDA:

  • [ ] 使用 find_program_address() 或 Anchor seeds 约束
  • [ ] Bump种子存储并重用
  • [ ] 不使用用户提供的bump

步骤4:账户验证扫描

# 查找账户反序列化
rg "try_from_slice|try_deserialize" programs/

# 应该在反序列化前看到所有者检查
rg "\.owner\s*==|\.owner\s*!=" programs/

对于每个使用的账户:

  • [ ] 反序列化前验证所有者
  • [ ] 权限账户进行签名检查
  • [ ] Anchor: 使用 Account<'info, T>Signer<'info>

步骤5:指令内省审查

# 查找指令内省使用
rg "load_instruction_at|load_current_index|get_instruction_relative" programs/

# 检查已检查版本
rg "load_instruction_at_checked|load_current_index_checked" programs/
  • [ ] 使用已检查函数(Solana 1.8.1+)
  • [ ] 使用相对索引
  • [ ] 适当的关联验证

步骤6:Trail of Bits Solana Lints

# 添加到Cargo.toml
[dependencies]
solana-program = "1.17"  # 使用最新版本

[lints.clippy]
# 启用Solana特定检查
# (如果可用,Trail of Bits solana-lints)

6. 报告格式

发现模板

## [关键] 任意CPI - 未检查的程序ID

**位置**: `programs/vault/src/lib.rs:145-160`(withdraw函数)

**描述**:
`withdraw` 函数执行CPI以转移SPL代币,但未验证提供的 `token_program` 账户是否为SPL代币程序。攻击者可以提供恶意程序,表面执行转移但实际窃取代币或执行未授权操作。

**易受攻击的代码**:
```rust
// lib.rs, 第145行
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
    let token_program = &ctx.accounts.token_program;

    // 错误:未验证token_program.key()!
    invoke(
        &spl_token::instruction::transfer(...),
        &[
            ctx.accounts.vault.to_account_info(),
            ctx.accounts.destination.to_account_info(),
            ctx.accounts.authority.to_account_info(),
            token_program.to_account_info(),  // 未验证
        ],
    )?;
    Ok(())
}

攻击场景:

  1. 攻击者部署恶意“代币程序”,记录转移指令但不执行
  2. 攻击者调用withdraw()提供恶意程序作为token_program
  3. 保险库的权限签署交易
  4. 恶意程序接收带有保险库签名的CPI
  5. 恶意程序现在可以冒充保险库并窃取真实代币

建议: 使用Anchor的 Program<'info, Token> 类型:

use anchor_spl::token::{Token, Transfer};

#[derive(Accounts)]
pub struct Withdraw<'info> {
    #[account(mut)]
    pub vault: Account<'info, TokenAccount>,
    #[account(mut)]
    pub destination: Account<'info, TokenAccount>,
    pub authority: Signer<'info>,
    pub token_program: Program<'info, Token>,  // 自动验证程序ID
}

pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
    let cpi_accounts = Transfer {
        from: ctx.accounts.vault.to_account_info(),
        to: ctx.accounts.destination.to_account_info(),
        authority: ctx.accounts.authority.to_account_info(),
    };

    let cpi_ctx = CpiContext::new(
        ctx.accounts.token_program.to_account_info(),
        cpi_accounts,
    );

    anchor_spl::token::transfer(cpi_ctx, amount)?;
    Ok(())
}

参考:

  • building-secure-contracts/not-so-smart-contracts/solana/arbitrary_cpi
  • Trail of Bits 检查: unchecked-cpi-program-id

---

## 7. 优先级指南

### 关键(立即修复)
- 任意CPI(攻击者控制的程序执行)
- 不当的PDA验证(账户欺骗)
- 缺失签名检查(未授权访问)

### 高(发布前修复)
- 缺失所有权检查(假账户数据)
- 系统变量账户检查(认证绕过,1.8.1之前)

### 中(审计中处理)
- 不当的指令内省(逻辑绕过)

---

## 8. 测试建议

### 单元测试
```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn test_rejects_wrong_program_id() {
        // 提供错误程序ID,应失败
    }

    #[test]
    #[should_panic]
    fn test_rejects_non_canonical_pda() {
        // 提供非规范bump,应失败
    }

    #[test]
    #[should_panic]
    fn test_requires_signer() {
        // 无签名调用,应失败
    }
}

集成测试(Anchor)

import * as anchor from "@coral-xyz/anchor";

describe("安全测试", () => {
  it("拒绝任意CPI", async () => {
    const fakeTokenProgram = anchor.web3.Keypair.generate();

    try {
      await program.methods
        .withdraw(amount)
        .accounts({
          tokenProgram: fakeTokenProgram.publicKey, // 错误程序
        })
        .rpc();

      assert.fail("应拒绝假程序");
    } catch (err) {
      // 预期失败
    }
  });
});

Solana Test Validator

# 运行本地验证器进行测试
solana-test-validator

# 部署和测试程序
anchor test

9. 附加资源


10. 快速参考清单

在完成Solana程序审计前:

CPI安全(关键):

  • [ ] 所有CPI调用在 invoke() 前验证程序ID
  • [ ] 不能使用用户提供的程序账户
  • [ ] Anchor: 使用 Program<'info, T> 类型

PDA安全(关键):

  • [ ] PDA使用 find_program_address() 或 Anchor seeds 约束
  • [ ] Bump种子存储并重用(非用户提供)
  • [ ] PDA账户验证为规范地址

账户验证(高):

  • [ ] 所有账户在反序列化前检查所有者
  • [ ] 原生: 验证 account.owner == 预期程序ID
  • [ ] Anchor: 使用 Account<'info, T> 类型

签名验证(关键):

  • [ ] 所有权限账户检查 is_signer
  • [ ] 原生: 验证 account.is_signer == true
  • [ ] Anchor: 使用 Signer<'info> 类型

系统变量安全(高):

  • [ ] 使用Solana 1.8.1+
  • [ ] 使用已检查函数: load_instruction_at_checked()
  • [ ] 系统变量地址验证

指令内省(中):

  • [ ] 使用相对索引进行关联
  • [ ] 适当验证相关指令间
  • [ ] 不能跨多个调用重用相同指令

测试:

  • [ ] 单元测试覆盖所有账户验证
  • [ ] 集成测试使用恶意输入
  • [ ] 本地验证器测试完成
  • [ ] Trail of Bits 检查启用并通过