mcpField
S
solana-vulnerability-scanner

solana-vulnerability-scanner

github.com/trailofbitsセキュリティ7.9k5.7k 回インストール今日更新
リポジトリ

solana-vulnerability-scanner について

ネイティブ Rust と Anchor の Solana プログラムを対象とするセキュリティレビュー用スキルです。アカウントモデルに関する6種類の脆弱性パターンを調べ、根拠、深刻度、修正案を報告します。

主な機能

  1. 01Solanaの脆弱性パターン6種を検査
  2. 02CPIのプログラムID検証を確認
  3. 03PDAとアカウント検証を確認
  4. 04署名者と命令チェックを検査
  5. 05根拠と修正案付きで問題を報告

活用シーン

  1. 01ネイティブRustまたはAnchorプログラムの監査
  2. 02CPIとPDA実装のレビュー
  3. 03リリース前のアカウント検証評価
  4. 04命令イントロスペクション処理の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/trailofbits/skills --skill solana-vulnerability-scanner

インストール後、Claude Code で/solana-vulnerability-scannerを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

agents/
openai.yaml
assets/
trail-of-bits-mark.svg
resources/
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.toml
  • 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 特有の重大な脆弱性パターンを6つ検査します。検出パターン、コード例、緩和策、テスト戦略の詳細については、VULNERABILITY_PATTERNS.md を参照してください。

パターンの概要:
  1. 任意の CPI ⚠️ 重大 - CPI 呼び出しでユーザー制御のプログラム ID を使用
  2. 不適切な PDA 検証 ⚠️ 重大 - 正規のバンプを指定せずに create_program_address を使用
  3. 所有者チェックの欠如 ⚠️ 高 - 所有者を検証せずにアカウントをデシリアライズ
  4. 署名者チェックの欠如 ⚠️ 重大 - is_signer チェックなしで権限操作を実行
  5. Sysvar アカウントチェック ⚠️ 高 - 偽造された Sysvar アカウント(Solana 1.8.1 より前)
  6. 不適切な命令イントロスペクション ⚠️ 中 - 再利用を許す絶対インデックス

コード例を含む完全な脆弱性パターンについては、VULNERABILITY_PATTERNS.md を参照してください。

7. スキャンのワークフロー

ステップ 1:プラットフォームの特定
  1. Solana プログラム(ネイティブまたは Anchor)であることを確認
  2. Solana のバージョンを確認(Sysvar のセキュリティには 1.8.1 以降が必要)
  3. プログラムのソースを特定(programs/*/src/lib.rs)
  4. フレームワークを特定(ネイティブまたは 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任意の CPIn/aこのプログラムはクロスプログラム呼び出しを行いません
2不適切な PDA 検証
3所有権チェックの欠落
4署名者チェックの欠落
5Sysvar アカウントチェック
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. 追加リソース


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 の全容です。

コメント