substrate-vulnerability-scanner について
Substrate/FRAME チェーンのセキュリティ監査向けスキルです。カスタムパレットやランタイムコードを対象に、7 種類の脆弱性パターンを確認し、網羅状況と修正案をまとめます。
主な機能
- 01Substrate 固有の 7 パターンを検査
- 02ディスパッチャブルと権限を確認
- 03ウェイトと算術処理を監査
- 04根拠付きの網羅状況を報告
- 05検出事項の修正案を提示
活用シーン
- 01カスタム Substrate パレットの監査
- 02ローンチ前のランタイム安全性評価
- 03未署名トランザクション検証の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trailofbits/ skills --skill substrate-vulnerability-scannerインストール後、Claude Code で/substrate-vulnerability-scannerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlassets/trail-of-bits-mark.svgresources/VULNERABILITY_PATTERNS.md詳細
Substrate 脆弱性スキャナー
1. 目的
Substrate のランタイムモジュール(パレット)を体系的にスキャンし、ノードのクラッシュ、DoS 攻撃、不正アクセスにつながる、プラットフォーム固有のセキュリティ脆弱性を検出します。このスキルには、Substrate/FRAME ベースのチェーンに固有の、7 つの重大な脆弱性パターンが含まれています。
2. このスキルを使用する場面
- カスタム Substrate パレットの監査
- FRAME ランタイムコードのレビュー
- Substrate チェーン(Polkadot パラチェーン、スタンドアロンチェーン)のローンチ前セキュリティ評価
- ディスパッチ可能なエクストリンシック関数の検証
- ウェイト計算関数のレビュー
- 署名なしトランザクションの検証ロジックの評価
3. プラットフォームの検出
ファイル拡張子と識別子
- Rust ファイル:
.rs
言語/フレームワークの識別子
// Substrate/FRAME indicators
#[pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config: frame_system::Config { }
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(10_000)]
pub fn example_function(origin: OriginFor<T>) -> DispatchResult { }
}
}
// Common patterns
DispatchResult, DispatchError
ensure!, ensure_signed, ensure_root
StorageValue, StorageMap, StorageDoubleMap
#[pallet::storage]
#[pallet::call]
#[pallet::weight]
#[pallet::validate_unsigned]
プロジェクト構造
pallets/*/lib.rs- パレットの実装runtime/lib.rs- ランタイムの設定benchmarking.rs- ウェイトベンチマークframe-*依存関係を含むCargo.toml
ツールのサポート
- cargo-fuzz:Rust のファズテスト
- test-fuzz:プロパティベーステスト用フレームワーク
- ベンチマークフレームワーク:組み込みのウェイト計算
- try-runtime:ランタイム移行のテスト
4. このスキルの動作
このスキルを実行すると、以下を行います。
- コードベースを検索し、Substrate パレットを探します
- 各パレットを分析し、7 つの脆弱性パターンを確認します
- 検出結果を報告します。ファイル参照と重大度を記載し、その上に各パターンの判定を示す網羅状況テーブルを掲載します
- 特定した問題ごとに修正方法を提示します
- ウェイト計算とオリジン検証を確認します
5. 脆弱性パターン(重大な 7 つのパターン)
Substrate/FRAME に固有の重大な脆弱性パターンを 7 つ確認します。詳細な検出パターン、コード例、緩和策、テスト戦略については、VULNERABILITY_PATTERNS.md を参照してください。
パターンの概要:
-
算術オーバーフロー ⚠️ 重大
+、-、*、/演算子はリリースモードでラップアラウンドしますchecked_*またはsaturating_*メソッドを使用する必要があります- 残高/トークンの計算、報酬/手数料の計算に影響します
-
パニックを避ける ⚠️ 重大 - DoS
- パニックが発生すると、ノードはブロックの処理を停止します
unwrap()、expect()、境界チェックのない配列インデックス指定は使用しないでください- すべてのユーザー入力を
ensure!で検証する必要があります
-
ウェイトと手数料 ⚠️ 重大 - DoS
- 不適切なウェイト設定はスパム攻撃を可能にします
- 可変コストの操作に固定ウェイトを設定すると、DoS 攻撃を招きます
- ベンチマークフレームワークを使用し、すべての入力パラメーターに上限を設定する必要があります
-
先に検証し、最後に書き込む ⚠️ 高(v0.9.25 より前)
- 検証前のストレージ書き込みは、エラー時にも保持されます(v0.9.25 より前)
- パターン:検証 → 書き込み → イベント発行
- v0.9.25 以降にアップグレードするか、手動で
#[transactional]を使用してください
-
署名なしトランザクションの検証 ⚠️ 高
- 検証が不十分だと、スパム攻撃やリプレイ攻撃を許すおそれがあります
- 署名付きトランザクションを優先してください
- 署名なしの場合:パラメーターの検証、リプレイ保護、送信元の認証を行ってください
-
不適切な乱数 ⚠️ 中
pallet_randomness_collective_flipは共謀に対して脆弱です- BABE の乱数(
pallet_babe::RandomnessFromOneEpochAgo)を使用する必要があります random_seed()ではなくrandom(subject)を使用してください
-
不適切なオリジン ⚠️ 重大
ensure_signedでは、特権操作を任意のユーザーが実行できてしまいますensure_rootまたはカスタムオリジン(ForceOrigin、AdminOrigin)を使用する必要があります- ランタイムでオリジン型を適切に設定する必要があります
コード例を含むすべての脆弱性パターンについては、VULNERABILITY_PATTERNS.md を参照してください。
6. スキャンのワークフロー
ステップ 1:プラットフォームの特定
- Substrate/FRAME フレームワークが使用されていることを確認します
- Substrate のバージョンを確認します(v0.9.25 以降ではトランザクション対応のストレージを使用できます)
- パレットの実装(
pallets/*/lib.rs)を特定します - ランタイムの設定(
runtime/lib.rs)を特定します
ステップ 2:ディスパッチ可能な処理の分析
各 #[pallet::call] 関数について:
- 算術演算:checked または saturating 演算を使用していますか?
- パニック:unwrap/expect/インデックスアクセスを使用していませんか?
- 重み:コストに比例し、入力が制限されていますか?
- Origin:適切な検証レベルですか?
- 検証:ストレージへの書き込み前にすべてのチェックを行っていますか?
ステップ 3:パニックの総点検
# Search for panic-prone patterns
rg "unwrap\(\)" pallets/
rg "expect\(" pallets/
rg "\[.*\]" pallets/ # Array indexing
rg " as u\d+" pallets/ # Type casts
rg "\.unwrap_or" pallets/
ステップ 4:算術演算の安全性チェック
# Find direct arithmetic
rg " \+ |\+=| - |-=| \* |\*=| / |/=" pallets/
# Should find checked/saturating alternatives instead
rg "checked_add|checked_sub|checked_mul|checked_div" pallets/
rg "saturating_add|saturating_sub|saturating_mul" pallets/
ステップ 5:重みの分析
- ベンチマークを実行します:
cargo test --features runtime-benchmarks - 重みが計算コストに見合っていることを確認します
- 入力パラメーターが制限されていることを確認します
- 重み計算関数を確認します
ステップ 6:Origin と権限の確認
# Find privileged operations
rg "ensure_signed" pallets/ | grep -E "pause|emergency|admin|force|sudo"
# Should use ensure_root or custom origins
rg "ensure_root|ForceOrigin|AdminOrigin" pallets/
ステップ 7:テストの確認
- 単体テストですべてのディスパッチ可能な処理をカバーしています
- パニック条件を対象としたファズテスト
- 重み計算のベンチマーク
- マイグレーションの try-runtime テスト
ステップ 8:網羅性の報告
§5 のすべてのパターンについて、該当事項が見つかったかどうかにかかわらず報告します。このスキルには Finding Template がないため、表をレポートの冒頭に置き、その後に検出事項を続けます。7 行すべてを含めてください:
| # | パターン | 判定 | 根拠 |
|---|---|---|---|
| 1 | 算術オーバーフロー | found | src/lib.rs:212 -- do_transfer 内で BalanceOf<T> に対して + を使用 |
| 2 | パニック禁止 | ||
| 3 | 重みと手数料 | ||
| 4 | 先に検証し、最後に書き込む | ||
| 5 | 符号なしトランザクションの検証 | ||
| 6 | 不適切なランダム性 | ||
| 7 | 不適切な Origin |
判定は次のいずれかです:
found—file:lineを示し、表の後で検出事項の詳細を記述します。clear— このパターンはこのパレットに適用され、パレットが適切に処理しています。検索対象としたマクロ、Origin チェック、または算術メソッドを明記し、読者が同じ検索を繰り返せるようにします。n/a— このパターンは該当しません。理由を一節で示します(「このパレットは符号なしトランザクションを受け付けない」など)。確認していないことはn/aではありません。パターン 4 はバージョンに依存する点(pre-v0.9.25)に注意してください。行を省略せず、パレットが対象とするランタイムのバージョンを記載します。
7 行未満の表はスキャンが不完全であり、そのように報告する必要があります。判定欄が空の行も同様に不完全です。上の 1 行目は記入例であり、レポートが完成するまで、すべての行を同じように埋めてください。7 つすべてが clear の判定でも、読者が対応に活用できる結果です。3 つのパターンだけを扱い、残り 4 つについて何も述べないレポートは、問題のないパレットのレポートとまったく同じに見えます。それを防ぐために、この表があります。
7. 優先度のガイドライン
重大(直ちに修正が必要)
- 算術オーバーフロー(トークンの生成、残高の操作)
- パニックによる DoS(ノードのクラッシュリスク)
- 不適切な Origin(権限が必要な操作の不正実行)
高(リリース前に修正)
- 不正確な重み(スパムによる DoS)
- 先に検証する原則への違反(状態の破損、pre-v0.9.25)
- 符号なし検証の問題(スパム、リプレイ攻撃)
中(監査で対応)
- 不適切なランダム性(影響は限定的ですが、操作される可能性があります)
8. テストに関する推奨事項
ファズテスト
// Use test-fuzz for property-based testing
#[cfg(test)]
mod tests {
use test_fuzz::test_fuzz;
#[test_fuzz]
fn fuzz_transfer(from: AccountId, to: AccountId, amount: u128) {
// Should never panic
let _ = Pallet::transfer(from, to, amount);
}
#[test_fuzz]
fn fuzz_no_panics(call: Call) {
// No dispatchable should panic
let _ = call.dispatch(origin);
}
}
ベンチマーク
# Run benchmarks to generate weights
cargo build --release --features runtime-benchmarks
./target/release/node benchmark pallet \
--chain dev \
--pallet pallet_example \
--extrinsic "*" \
--steps 50 \
--repeat 20
try-runtime
# Test runtime upgrades
cargo build --release --features try-runtime
try-runtime --runtime ./target/release/wbuild/runtime.wasm \
on-runtime-upgrade live --uri wss://rpc.polkadot.io
9. 追加リソース
- セキュアなコントラクトの構築:
building-secure-contracts/not-so-smart-contracts/substrate/ - Substrate ドキュメント:https://docs.substrate.io/
- FRAME ドキュメント:https://paritytech.github.io/substrate/master/frame_support/
- test-fuzz:https://github.com/trailofbits/test-fuzz
- Substrate StackExchange:https://substrate.stackexchange.com/
10. クイックリファレンスチェックリスト
Substrate パレットの監査を完了する前に、次を確認してください。
算術の安全性(重大):
- ディスパッチ可能関数で
+、-、*、/演算子を直接使用していない - すべての算術演算で
checked_*またはsaturating_*を使用している - 型変換ではエラー処理を伴う
try_into()を使用している
パニックの防止(重大):
- ディスパッチ可能関数で
unwrap()やexpect()を使用していない - 境界チェックなしで配列/スライスを直接インデックス指定していない
- すべてのユーザー入力を
ensure!で検証している - 除算処理で除数がゼロでないことを確認している
ウェイトと DoS(重大):
- ウェイトが計算コストに比例している
- 入力パラメーターに最大値の上限を設定している
- ウェイトの決定にベンチマークを使用している
- 高コストの処理を無料(ウェイトゼロ)で実行できない
アクセス制御(重大):
- 特権操作で
ensure_rootまたはカスタムオリジンを使用している -
ensure_signedはユーザーレベルの操作にのみ使用している - オリジンの型をランタイムで適切に設定している
- 本番環境に移行する前に Sudo パレットを削除している
ストレージの安全性(高):
- Substrate v0.9.25 以降を使用している、または手動で
#[transactional]を指定している - ストレージへの書き込み前に検証している
- 操作の成功後にイベントを発行している
その他(中):
- 可能であれば、署名なしトランザクションの代わりに署名付きの方法を使用している
- 署名なしの場合、適切な検証、リプレイ保護、認証を実装している
- RandomnessCollectiveFlip ではなく BABE のランダム性を使用している
- ランダム性には
random_seed()ではなくrandom(subject)を使用している
テスト:
- すべてのディスパッチ可能関数にユニットテストがある
- パニックを見つけるためのファズテストを実施している
- ベンチマークを生成して検証している
- マイグレーションに対して try-runtime テストを実施している
- 7 行すべてを含み、それぞれに理由とともに
found、clear、n/aのいずれかの判定を記載したカバレッジ表を出力している
11. 退けるべき合理化
- 「このパレットは小さいので、ほとんどのパターンは明らかに当てはまりません。」 誰にとって明らかなのでしょうか。
n/aはひとつの節を追加するだけで、判断をレビュー可能にします。何も書かなければ記録は残らず、読者には未確認の場合と区別できません。 - 「警告なしでコンパイルできるので、算術演算は問題ありません。」 リリースビルドではオーバーフローが黙って発生します。
Balanceに対する+は、コンパイラーが何も指摘しなくてもパターン 1 に該当し、本番ノードではデバッグアサーションも実行されません。 - 「このパレットに重要なパターンは確認しました。」 どのパターンが重要かを判断すること自体がスキャンであり、開始前の前提ではありません。行を省くのではなく、表を完成させてから重大度順に並べてください。
- 「指摘事項がないので、報告することはありません。」 指摘事項がゼロのスキャンでも、カバレッジ表全体を出力してください。その表こそが成果物です。調査したパレットと、ざっと目を通しただけのパレットとを区別するためのものです。
- 「
ensure_signedはオリジンチェックです。」 これで確認できるのは「誰か」が署名したことだけであり、適切な人物が署名したことではありません。パターン 7 が問題にしているのは、どのオリジンが必要かです。ensure_rootが必要な箇所でensure_signedを使うのは、この合理化には合致してもチェックには失敗します。 - 「ウェイトはベンチマークで測定したので正しいです。」 ベンチマークで測定できるのは、実際に実行した経路です。最悪ケースがストレージサイズや上限のないループに左右されるディスパッチ可能関数は、ベンチマークが添付されていてもパターン 3 に該当します。