solana-vulnerability-scanner について
ネイティブ Rust と Anchor の Solana プログラムを対象とするセキュリティレビュー用スキルです。アカウントモデルに関する6種類の脆弱性パターンを調べ、根拠、深刻度、修正案を報告します。
主な機能
- 01Solanaの脆弱性パターン6種を検査
- 02CPIのプログラムID検証を確認
- 03PDAとアカウント検証を確認
- 04署名者と命令チェックを検査
- 05根拠と修正案付きで問題を報告
活用シーン
- 01ネイティブRustまたはAnchorプログラムの監査
- 02CPIとPDA実装のレビュー
- 03リリース前のアカウント検証評価
- 04命令イントロスペクション処理の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trailofbits/ skills --skill solana-vulnerability-scannerインストール後、Claude Code で/solana-vulnerability-scannerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlassets/trail-of-bits-mark.svgresources/VULNERABILITY_PATTERNS.md詳細
Solana 脆弱性スキャナー
1. 目的
Solana プログラム(ネイティブおよび Anchor フレームワーク)を体系的にスキャンし、クロスプログラム呼び出し、アカウント検証、プログラム派生アドレスに関する、プラットフォーム固有のセキュリティ脆弱性を検出します。このスキルでは、Solana のアカウントモデル特有の6つの重大な脆弱性パターンを検出します。
2. このスキルを使用する場面
- Solana プログラム(ネイティブ Rust または Anchor)の監査
- クロスプログラム呼び出し(CPI)ロジックのレビュー
- プログラム派生アドレス(PDA)の実装の検証
- Solana プロトコルのローンチ前セキュリティ評価
- アカウント検証パターンのレビュー
- 命令のイントロスペクションロジックの評価
3. プラットフォームの検出
ファイル拡張子と識別子
- Rust ファイル:
.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- プログラムの実装Anchor.toml- Anchor の設定solana-programまたはanchor-langを含むCargo.tomltests/- プログラムのテスト
ツールのサポート
- Trail of Bits Solana Lints:Solana 用 Rust リンター
- インストール:Cargo.toml に追加
- anchor test:組み込みのテストフレームワーク
- Solana Test Validator:ローカルテスト環境
4. このスキルの仕組み
このスキルを呼び出すと、以下を実行します。
- コードベースを検索して Solana/Anchor プログラムを特定します
- 各プログラムを分析して、6つの脆弱性パターンを検査します
- 検出結果を報告します。ファイル参照と重大度を示し、その上に各パターンの判定を記載した網羅性テーブルを表示します
- 特定した問題ごとに修正方法を提示します
- アカウント検証と CPI のセキュリティを確認します
5. 出力例
6. 脆弱性パターン(6パターン)
Solana 特有の重大な脆弱性パターンを6つ検査します。検出パターン、コード例、緩和策、テスト戦略の詳細については、VULNERABILITY_PATTERNS.md を参照してください。
パターンの概要:
- 任意の CPI ⚠️ 重大 - CPI 呼び出しでユーザー制御のプログラム ID を使用
- 不適切な PDA 検証 ⚠️ 重大 - 正規のバンプを指定せずに create_program_address を使用
- 所有者チェックの欠如 ⚠️ 高 - 所有者を検証せずにアカウントをデシリアライズ
- 署名者チェックの欠如 ⚠️ 重大 - is_signer チェックなしで権限操作を実行
- Sysvar アカウントチェック ⚠️ 高 - 偽造された Sysvar アカウント(Solana 1.8.1 より前)
- 不適切な命令イントロスペクション ⚠️ 中 - 再利用を許す絶対インデックス
コード例を含む完全な脆弱性パターンについては、VULNERABILITY_PATTERNS.md を参照してください。
7. スキャンのワークフロー
ステップ 1:プラットフォームの特定
- Solana プログラム(ネイティブまたは Anchor)であることを確認
- Solana のバージョンを確認(Sysvar のセキュリティには 1.8.1 以降が必要)
- プログラムのソースを特定(
programs/*/src/lib.rs) - フレームワークを特定(ネイティブまたは Anchor)
ステップ 2:CPI セキュリティのレビュー
# Find all CPI calls
rg "invoke\(|invoke_signed\(" programs/
# Check for program ID validation before each
# Should see program ID checks immediately before invoke
各 CPI について:
- 呼び出し前にプログラム ID を検証している
- ユーザー制御のプログラムアカウントを渡せない
- Anchor:
Program<'info, T>型を使用している
ステップ 3:PDA 検証の確認
# Find PDA usage
rg "find_program_address|create_program_address" programs/
rg "seeds.*bump" programs/
# Anchor: Check for seeds constraints
rg "#\[account.*seeds" programs/
各 PDA について:
-
find_program_address()または Anchor のseeds制約を使用している - バンプシードを保存して再利用している
- ユーザー指定のバンプを使用していない
ステップ 4:アカウント検証の総点検
# Find account deserialization
rg "try_from_slice|try_deserialize" programs/
# Should see owner checks before deserialization
rg "\.owner\s*==|\.owner\s*!=" programs/
使用する各アカウントについて:
- デシリアライズ前に所有者を検証している
- 権限アカウントに対して署名者チェックを行っている
- Anchor:
Account<'info, T>およびSigner<'info>を使用している
ステップ 5:命令イントロスペクションのレビュー
# 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/
- チェック機能を使用している(Solana 1.8.1 以降)
- 相対インデックスを使用している
- 相関関係を適切に検証している
ステップ 6:Trail of Bits Solana Lints
# 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)
8. レポート形式
カバレッジ表
§6 のすべてのパターンについて、該当箇所が見つかったかどうかにかかわらず報告してください。調査結果の前に、6 行すべてを含む次の表を出力してください。
| # | パターン | 判定 | 根拠 |
|---|---|---|---|
| 1 | 任意の CPI | n/a | このプログラムはクロスプログラム呼び出しを行いません |
| 2 | 不適切な PDA 検証 | ||
| 3 | 所有権チェックの欠落 | ||
| 4 | 署名者チェックの欠落 | ||
| 5 | Sysvar アカウントチェック | ||
| 6 | 不適切な命令イントロスペクション |
判定は次のいずれかです。
found—file:lineを引用し、以下に指摘事項の全容を記載してください。clear— このパターンはこのプログラムに該当し、プログラムが適切に処理しています。読者が同じ調査を再現できるよう、検索した制約、アカウント型、またはチェックを明記してください。n/a— このパターンはここでは該当しません。理由を 1 つの節で示してください(「このプログラムは CPI 呼び出しを行いません」など)。調査していないことはn/aの理由になりません。パターン 5 はバージョンに依存します(Solana 1.8.1 より前)。行を省略せず、プログラムが対象とするsolana-programのバージョンを示してください。「1.17 が対象で、上流で修正済み」と「調査していない」は、それ以外では同じ回答になってしまいます。
6 行未満の表は調査が不完全であり、その旨を報告する必要があります。Verdict セルが空の行も同様に不完全です。上記の 1 行目は記入例です。レポートを完成させる前に、すべての行を同じように記入してください。6 つすべてが clear という結果も、読者が対応に活用できます。2 つのパターンだけを扱い、残り 4 つに触れないレポートは、問題のないプログラムのレポートとまったく同じように見えます。この表は、それを防ぐためにあります。
指摘事項のテンプレート
## [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**:
1. Attacker deploys malicious "token program" that logs transfer instruction but doesn't execute it
2. Attacker calls withdraw() providing malicious program as token_program
3. Vault's authority signs the transaction
4. Malicious program receives CPI with vault's signature
5. Malicious program can now impersonate vault and drain real tokens
**Recommendation**:
Use Anchor's `Program<'info, Token>` type:
```rust
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`
9. 優先度のガイドライン
重大(直ちに修正が必要)
- 任意の CPI(攻撃者が制御するプログラムの実行)
- 不適切な PDA 検証(アカウントの偽装)
- 署名者チェックの欠落(不正アクセス)
高(ローンチ前に修正)
- 所有権チェックの欠落(偽のアカウントデータ)
- Sysvar アカウントチェック(認証の回避、1.8.1 より前)
中(監査時に対応)
- 不適切な命令イントロスペクション(ロジックの回避)
10. テストに関する推奨事項
単体テスト
#[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
}
}
統合テスト(Anchor)
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
}
});
});
Solana Test Validator
# Run local validator for testing
solana-test-validator
# Deploy and test program
anchor test
11. 追加リソース
- セキュアなコントラクトの構築:
building-secure-contracts/not-so-smart-contracts/solana/ - Trail of Bits の Solana Lints: https://github.com/trailofbits/solana-lints
- Anchor のドキュメント: https://www.anchor-lang.com/
- Solana Program Library: https://github.com/solana-labs/solana-program-library
- Solana Cookbook: https://solanacookbook.com/
12. クイックリファレンスチェックリスト
Solana プログラムの監査を完了する前に、次の項目を確認してください。
CPI セキュリティ(重大):
- すべての CPI 呼び出しで、
invoke()の前にプログラム ID を検証している - ユーザーが提供したプログラムアカウントを使用できない
- Anchor:
Program<'info, T>型を使用している
PDA セキュリティ(重大):
- PDA で
find_program_address()または Anchor のseeds制約を使用している - バンプシードを保存して再利用している(ユーザーが提供したものではない)
- PDA アカウントを正規アドレスと照合して検証している
アカウント検証(高):
- すべてのアカウントで、デシリアライズの前にオーナーを確認している
- ネイティブ:
account.owner == expected_program_idを検証している - Anchor:
Account<'info, T>型を使用している
署名者検証(重大):
- すべての権限アカウントで
is_signerを確認している - ネイティブ:
account.is_signer == trueを検証している - Anchor:
Signer<'info>型を使用している
Sysvar セキュリティ(高):
- Solana 1.8.1 以降を使用している
-
load_instruction_at_checked()などのチェック済み関数を使用している - Sysvar アドレスを検証している
命令イントロスペクション(中):
- 関連付けには相対インデックスを使用している
- 関連する命令間で適切な検証を行っている
- 同じ命令を複数の呼び出しで再利用できない
テスト:
- すべてのアカウント検証を網羅するユニットテストがある
- 悪意のある入力を使った統合テストがある
- ローカルバリデーターでのテストが完了している
- Trail of Bits の lint が有効で、すべてパスしている
- 全 6 行のカバレッジ表を出力し、各行に理由付きで
found、clear、n/aのいずれかの判定を記載している
13. 退けるべき合理化
- 「プログラムは小さいので、ほとんどのパターンが当てはまらないのは明らかです。」 誰にとって明らかなのでしょうか。
n/aの記載に必要なのは 1 つの節だけで、判断をレビュー可能にします。何も書かなければ何も記録されず、読者には未確認の場合との区別がつきません。 - 「Trail of Bits の lint がパスしたので、プログラムに問題はありません。」 lint が対象にするのは、この 6 つのパターンの一部です。lint がすべてパスしたことは証拠の 1 行であり、検査対象外のパターンについての判定ではありません。どのパターンを対象にしたのかを明記してください。
- 「このプログラムに関係するパターンは確認しました。」 どのパターンが関係するか判断すること自体がスキャンであり、開始前提ではありません。表を完成させてから重大度順に並べてください。行を省いてはいけません。
- 「検出事項はないので、報告することはありません。」 検出事項がゼロでも、スキャンでは完全なカバレッジ表を出力します。この表が成果物です。調査済みのプログラムと、ざっと見ただけのプログラムを区別するものです。
- 「アカウント検証は Anchor が処理します。」 制約を具体的に示してください。Anchor が検証するのは、アカウント構造体で宣言された内容だけです。
#[account(mut)]はオーナー確認ではなく、UncheckedAccountは検証を完全に省略します。フレームワークではなく、属性を引用してください。 - 「PDA の導出によって安全性は確保されます。」 導出は検証ではありません。正規のバンプなしで
create_program_addressを使うと、複数の有効なアドレスが許容されます。これがパターン 2 の全容です。