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-program或anchor-langtests/- 程序测试
工具支持
- Trail of Bits Solana Lints: Solana的Rust代码检查器
- 安装: 添加到Cargo.toml
- anchor test: 内置测试框架
- Solana Test Validator: 本地测试环境
4. 这个技能如何工作
当调用时,我将:
- 搜索你的代码库 寻找Solana/Anchor程序
- 分析每个程序 针对6个漏洞模式
- 报告发现 带有文件引用和严重性
- 提供修复 针对每个识别出的问题
- 检查账户验证 和CPI安全
5. 漏洞模式(6个模式)
我检查6个Solana独有的关键漏洞模式。有关详细的检测模式、代码示例、缓解措施和测试策略,请参阅VULNERABILITY_PATTERNS.md。
模式摘要:
- 任意CPI ⚠️ 关键 - CPI调用中用户控制的程序ID
- 不当的PDA验证 ⚠️ 关键 - 使用create_program_address而不带规范bump
- 缺失所有权检查 ⚠️ 高 - 反序列化账户而不进行所有者验证
- 缺失签名检查 ⚠️ 关键 - 权限操作而不进行is_signer检查
- 系统变量账户检查 ⚠️ 高 - 欺骗的系统变量账户(Solana 1.8.1之前)
- 不当的指令内省 ⚠️ 中 - 允许重用的绝对索引
有关完整的漏洞模式及代码示例,请参阅VULNERABILITY_PATTERNS.md。
5. 扫描工作流
步骤1:平台识别
- 验证Solana程序(原生或Anchor)
- 检查Solana版本(1.8.1+ 用于系统变量安全)
- 定位程序源(
programs/*/src/lib.rs) - 识别框架(原生 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()或 Anchorseeds约束 - [ ] 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(())
}
攻击场景:
- 攻击者部署恶意“代币程序”,记录转移指令但不执行
- 攻击者调用withdraw()提供恶意程序作为token_program
- 保险库的权限签署交易
- 恶意程序接收带有保险库签名的CPI
- 恶意程序现在可以冒充保险库并窃取真实代币
建议:
使用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. 附加资源
- Building Secure Contracts:
building-secure-contracts/not-so-smart-contracts/solana/ - Trail of Bits Solana Lints: https://github.com/trailofbits/solana-lints
- Anchor Documentation: https://www.anchor-lang.com/
- Solana Program Library: https://github.com/solana-labs/solana-program-library
- Solana Cookbook: https://solanacookbook.com/
10. 快速参考清单
在完成Solana程序审计前:
CPI安全(关键):
- [ ] 所有CPI调用在
invoke()前验证程序ID - [ ] 不能使用用户提供的程序账户
- [ ] Anchor: 使用
Program<'info, T>类型
PDA安全(关键):
- [ ] PDA使用
find_program_address()或 Anchorseeds约束 - [ ] 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 检查启用并通过