solana-vulnerability-scanner by trailofbits/skills
npx skills add https://github.com/trailofbits/skills --skill solana-vulnerability-scanner系统性地扫描 Solana 程序(原生和 Anchor 框架),查找与跨程序调用、账户验证和程序派生地址相关的平台特定安全漏洞。此技能编码了 6 种 Solana 账户模型特有的关键漏洞模式。
.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(...)] with constraints
seeds, bump
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
programs/*/src/lib.rs - 程序实现Anchor.toml - Anchor 配置solana-program 或 anchor-lang 的 Cargo.tomltests/ - 程序测试调用时,我将:
我检查 6 种 Solana 特有的关键漏洞模式。有关详细的检测模式、代码示例、缓解措施和测试策略,请参阅 VULNERABILITY_PATTERNS.md。
create_program_address 而未使用规范 bumpis_signer 检查有关包含代码示例的完整漏洞模式,请参阅 VULNERABILITY_PATTERNS.md。
programs/*/src/lib.rs)# 查找所有 CPI 调用
rg "invoke\(|invoke_signed\(" programs/
# 检查每次调用前的程序 ID 验证
# 应在 invoke 之前立即看到程序 ID 检查
对于每个 CPI:
Program<'info, T> 类型# 查找 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 约束# 查找账户反序列化
rg "try_from_slice|try_deserialize" programs/
# 应在反序列化前看到所有者检查
rg "\.owner\s*==|\.owner\s*!=" programs/
对于每个使用的账户:
Account<'info, T> 和 Signer<'info># 查找指令内省使用
rg "load_instruction_at|load_current_index|get_instruction_relative" programs/
# 检查已检查的版本
rg "load_instruction_at_checked|load_current_index_checked" programs/
# 添加到 Cargo.toml
[dependencies]
solana-program = "1.17" # 使用最新版本
[lints.clippy]
# 启用 Solana 特定的代码检查
# (如果可用,使用 Trail of Bits 的 solana-lints)
## [严重] 任意 CPI - 未检查的程序 ID
**位置**:`programs/vault/src/lib.rs:145-160`(withdraw 函数)
**描述**:
`withdraw` 函数执行 CPI 以转移 SPL 代币,但未验证提供的 `token_program` 账户是否确实是 SPL Token 程序。攻击者可以提供一个恶意程序,该程序看似执行转账,但实际上窃取代币或执行未经授权的操作。
**易受攻击的代码**:
```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(())
}
攻击场景:
建议:使用 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(())
}
参考:
unchecked-cpi-program-id
---
## 7. 优先级指南
### 严重(需要立即修复)
* 任意 CPI(攻击者控制的程序执行)
* 不当的 PDA 验证(账户欺骗)
* 缺失签名者检查(未经授权的访问)
### 高(上线前修复)
* 缺失所有权检查(虚假账户数据)
* Sysvar 账户检查(身份验证绕过,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() {
// 调用时无签名,应该失败
}
}
import * as anchor from "@coral-xyz/anchor";
describe("security tests", () => {
it("rejects arbitrary 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
# 部署并测试程序
anchor test
building-secure-contracts/not-so-smart-contracts/solana/完成 Solana 程序审计前:
CPI 安全(严重):
invoke() 前验证程序 IDProgram<'info, T> 类型PDA 安全(严重):
find_program_address() 或 Anchor 的 seeds 约束账户验证(高):
account.owner == expected_program_idAccount<'info, T> 类型签名者验证(严重):
is_signeraccount.is_signer == trueSigner<'info> 类型Sysvar 安全(高):
load_instruction_at_checked()指令内省(中):
测试:
每周安装
1.3K
仓库
GitHub 星标
3.9K
首次出现
2026 年 1 月 19 日
安全审计
安装于
opencode1.1K
claude-code1.1K
gemini-cli1.1K
codex1.1K
cursor1.0K
github-copilot1.0K
Systematically scan Solana programs (native and Anchor framework) for platform-specific security vulnerabilities related to cross-program invocations, account validation, and program-derived addresses. This skill encodes 6 critical vulnerability patterns unique to Solana's account model.
.rs// Native Solana program indicators
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
pubkey::Pubkey,
program::invoke,
program::invoke_signed,
};
entrypoint!(process_instruction);
// Anchor framework indicators
use anchor_lang::prelude::*;
#[program]
pub mod my_program {
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// Program logic
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub authority: Signer<'info>,
}
// Common patterns
AccountInfo, Pubkey
invoke(), invoke_signed()
Signer<'info>, Account<'info>
#[account(...)] with constraints
seeds, bump
programs/*/src/lib.rs - Program implementationAnchor.toml - Anchor configurationCargo.toml with solana-program or anchor-langtests/ - Program testsWhen invoked, I will:
I check for 6 critical vulnerability patterns unique to Solana. For detailed detection patterns, code examples, mitigations, and testing strategies, see VULNERABILITY_PATTERNS.md.
For complete vulnerability patterns with code examples, see VULNERABILITY_PATTERNS.md.
programs/*/src/lib.rs)# Find all CPI calls
rg "invoke\(|invoke_signed\(" programs/
# Check for program ID validation before each
# Should see program ID checks immediately before invoke
For each CPI:
Program<'info, T> type# Find PDA usage
rg "find_program_address|create_program_address" programs/
rg "seeds.*bump" programs/
# Anchor: Check for seeds constraints
rg "#\[account.*seeds" programs/
For each PDA:
find_program_address() or Anchor seeds constraint# Find account deserialization
rg "try_from_slice|try_deserialize" programs/
# Should see owner checks before deserialization
rg "\.owner\s*==|\.owner\s*!=" programs/
For each account used:
Account<'info, T> and Signer<'info># Find instruction introspection usage
rg "load_instruction_at|load_current_index|get_instruction_relative" programs/
# Check for checked versions
rg "load_instruction_at_checked|load_current_index_checked" programs/
# Add to Cargo.toml
[dependencies]
solana-program = "1.17" # Use latest version
[lints.clippy]
# Enable Solana-specific lints
# (Trail of Bits solana-lints if available)
## [CRITICAL] Arbitrary CPI - Unchecked Program ID
**Location**: `programs/vault/src/lib.rs:145-160` (withdraw function)
**Description**:
The `withdraw` function performs a CPI to transfer SPL tokens without validating that the provided `token_program` account is actually the SPL Token program. An attacker can provide a malicious program that appears to perform a transfer but actually steals tokens or performs unauthorized actions.
**Vulnerable Code**:
```rust
// lib.rs, line 145
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
let token_program = &ctx.accounts.token_program;
// WRONG: No validation of 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(), // UNVALIDATED
],
)?;
Ok(())
}
Attack Scenario :
Recommendation : Use Anchor's Program<'info, Token> type:
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>, // Validates program ID automatically
}
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(())
}
References :
building-secure-contracts/not-so-smart-contracts/solana/arbitrary_cpi
Trail of Bits lint: unchecked-cpi-program-id
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn test_rejects_wrong_program_id() {
// Provide wrong program ID, should fail
}
#[test]
#[should_panic]
fn test_rejects_non_canonical_pda() {
// Provide non-canonical bump, should fail
}
#[test]
#[should_panic]
fn test_requires_signer() {
// Call without signature, should fail
}
}
import * as anchor from "@coral-xyz/anchor";
describe("security tests", () => {
it("rejects arbitrary CPI", async () => {
const fakeTokenProgram = anchor.web3.Keypair.generate();
try {
await program.methods
.withdraw(amount)
.accounts({
tokenProgram: fakeTokenProgram.publicKey, // Wrong program
})
.rpc();
assert.fail("Should have rejected fake program");
} catch (err) {
// Expected to fail
}
});
});
# Run local validator for testing
solana-test-validator
# Deploy and test program
anchor test
building-secure-contracts/not-so-smart-contracts/solana/Before completing Solana program audit:
CPI Security (CRITICAL) :
invoke()Program<'info, T> typePDA Security (CRITICAL) :
find_program_address() or Anchor seeds constraintAccount Validation (HIGH) :
account.owner == expected_program_idAccount<'info, T> typeSigner Validation (CRITICAL) :
is_signeraccount.is_signer == trueSigner<'info> typeSysvar Security (HIGH) :
load_instruction_at_checked()Instruction Introspection (MEDIUM) :
Testing :
Weekly Installs
1.3K
Repository
GitHub Stars
3.9K
First Seen
Jan 19, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode1.1K
claude-code1.1K
gemini-cli1.1K
codex1.1K
cursor1.0K
github-copilot1.0K