ton-vulnerability-scanner について
ton-vulnerability-scannerは、TONのFunCスマートコントラクト向けセキュリティレビュー用スキルです。TON固有の3種類のリスクを調べ、網羅状況、重大度、ファイル位置、修正案を報告します。
主な機能
- 01整数をブール値として使うロジックの検査
- 02Jetton通知の送信元検証の確認
- 03TON転送とGas処理の確認
- 043パターンすべての判定を報告
- 05問題箇所と修正案の提示
活用シーン
- 01ローンチ前のFunCコントラクト監査
- 02Jetton転送ハンドラーのレビュー
- 03TON転送とGasロジックの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trailofbits/ skills --skill ton-vulnerability-scannerインストール後、Claude Code で/ton-vulnerability-scannerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlassets/trail-of-bits-mark.svgresources/VULNERABILITY_PATTERNS.md詳細
TON 脆弱性スキャナー
1. 目的
FunC で記述された TON ブロックチェーンのスマートコントラクトを体系的にスキャンし、ブール論理、Jetton トークンの処理、ガス管理に関するプラットフォーム固有のセキュリティ脆弱性を検出します。このスキルでは、TON のアーキテクチャ固有の 3 つの重大な脆弱性パターンを扱います。
2. このスキルを使用する場面
- TON スマートコントラクト(FunC 言語)の監査
- Jetton トークン実装のレビュー
- トークン転送通知ハンドラーの検証
- TON dApp のローンチ前セキュリティ評価
- ガス転送ロジックのレビュー
- ブール条件処理の評価
3. プラットフォームの検出
ファイル拡張子と指標
- FunC ファイル:
.fc、.func
言語/フレームワークのマーカー
;; FunC contract indicators
#include "imports/stdlib.fc";
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
;; Contract logic
}
() recv_external(slice in_msg) impure {
;; External message handler
}
;; Common patterns
send_raw_message()
load_uint(), load_msg_addr(), load_coins()
begin_cell(), end_cell(), store_*()
transfer_notification operation
op::transfer, op::transfer_notification
.store_uint().store_slice().store_coins()
プロジェクト構成
contracts/*.fc- FunC コントラクトのソースwrappers/*.ts- TypeScript ラッパーtests/*.spec.ts- コントラクトのテストton.config.tsまたはwasm.config.ts- TON プロジェクトの設定
ツールのサポート
- TON Blueprint:TON 向け開発フレームワーク
- toncli:TON コントラクト向け CLI ツール
- ton-compiler:FunC コンパイラー
- 主に手動レビュー(自動化ツールは限定的)
4. このスキルの動作
呼び出されると、次の処理を行います。
- コードベースを検索し、FunC/Tact コントラクトを探します
- 各コントラクトを分析し、3 つの脆弱性パターンを確認します
- ファイル参照と重大度を含む調査結果を報告し、その前に各パターンの判定を記載したカバレッジ表を表示します
- 特定した各問題の修正方法を提示します
- 3 つすべてのパターンと各判定を含むカバレッジ表を出力します
5. 出力例
脆弱性が見つかった場合、次のようなレポートが表示されます。
=== TON VULNERABILITY SCAN RESULTS ===
Project: my-ton-contract
Files Scanned: 3 (.fc, .tact)
Vulnerabilities Found: 2
Coverage: 3/3 patterns reported
1 Integer as Boolean .............. found contracts/wallet.fc:45
2 Fake Jetton Contract ............ found contracts/staking.fc:85
3 Forward TON Without Gas Check ... clear forward amounts fixed at 0.05 TON
---
[CRITICAL] Fake Jetton Contract - Missing Sender Validation
File: contracts/staking.fc:85
Pattern: transfer_notification sender not checked against the stored Jetton wallet
6. 脆弱性パターン(3 パターン)
TON 固有の重大な脆弱性パターンを 3 つ確認します。詳細な検出パターン、コード例、緩和策、テスト戦略については、VULNERABILITY_PATTERNS.md を参照してください。
パターンの概要:
- 整数をブール値として使用 ⚠️ 高 - 正の整数を true として使用しています。FunC における true は -1 です
- 偽 Jetton コントラクト ⚠️ 重大 -
transfer_notificationの送信元が検証されていません - ガス確認なしで TON を転送 ⚠️ 高 - 実行の残りに必要なガスを確保せずに転送しています
コード例を含む脆弱性パターンの全一覧については、VULNERABILITY_PATTERNS.md を参照してください。
7. スキャンのワークフロー
ステップ 1:プラットフォームの特定
- FunC 言語を確認します(
.fcまたは.funcファイル) - TON Blueprint または toncli のプロジェクト構成を確認します
- コントラクトのソースファイルを特定します
- Jetton 関連のコントラクトを特定します
ステップ 2:ブール論理のレビュー
# Find boolean-like variables
rg "int.*is_|int.*has_|int.*flag|int.*enabled" contracts/
# Check for positive integers used as booleans
rg "= 1;|return 1;" contracts/ | grep -E "is_|has_|flag|enabled|valid"
# Look for NOT operations on boolean-like values
rg "~.*\(|~ " contracts/
各ブール値について:
- true に -1、false に 0 を使用している
- 1 やその他の正の整数を使用していない
- 論理演算が正しく機能する
ステップ 3:Jetton ハンドラーの分析
# Find transfer_notification handlers
rg "transfer_notification|op::transfer_notification" contracts/
各 Jetton ハンドラーについて:
- 送信元アドレスを検証している
- 送信元を、保存済みの Jetton ウォレットアドレスと照合している
- 送信元を検証せずに
forward_payloadを信頼することがない - Jetton ウォレットアドレスを設定する管理者関数がある
ステップ 4:ガス/転送量のレビュー
# Find forward amount usage
rg "forward_ton_amount|forward_amount" contracts/
rg "load_coins\(\)" contracts/
# Find send_raw_message calls
rg "send_raw_message" contracts/
各送信メッセージについて:
- 転送量が固定または上限付きである
- または、ユーザー指定の量を msg_value に照らして検証している
- コントラクト残高を流出させることができない
- 適切な send_raw_message フラグを使用している
ステップ 5:手動レビュー
TON コントラクトには、徹底した手動レビューが必要です。
~、&、|演算子を使用したブール論理- メッセージの解析と検証
- ガスの経済性と手数料の計算
- ストレージ操作とデータのシリアライズ
8. 報告形式
カバレッジ表
§6 のすべてのパターンについて、該当箇所が見つかったかどうかにかかわらず報告してください。調査結果より前に、次の表を出力し、3 行すべてを含めてください。
| # | パターン | 判定 | 根拠 |
|---|---|---|---|
| 1 | 整数をブール値として使用 | clear | is_/has_/flag を検索し、すべて -1 に設定されていることを確認 |
| 2 | 偽の Jetton コントラクト | ||
| 3 | ガスチェックなしで TON を転送 |
判定は次のいずれかです。
found—file:lineを示し、以下に発見事項の全容を記述します。clear— このパターンはこのコントラクトに該当しますが、コントラクトは対処しています。読者が同じ検索を再実行できるよう、検索した関数、保存済みアドレス、またはチェックを明記してください。n/a— このパターンはここでは該当しません。理由を 1 節で示してください(「このコントラクトは Jetton の転送通知を処理しない」など)。調査していないことはn/aの理由にはなりません。
3 つのパターンは少数です。そのため、不完全な表は、むしろ見過ごしにくくなります。1 つのパターンだけを扱い、残りの 2 つについて何も記載しないレポートは、まるで問題のないコントラクトであるかのように読めてしまいます。3 つすべてが clear の場合も、必ず 3 行すべてを出力してください。判定セルが空の行も同様に不完全です。上の 1 行目は形式を示すために記入済みです。レポートを完成させる前に、すべての行を同じように記入してください。
発見事項のテンプレート
## [CRITICAL] Fake Jetton Contract - Missing Sender Validation
**Location**: `contracts/staking.fc:85-95` (recv_internal, transfer_notification handler)
**Description**:
The `transfer_notification` operation handler does not validate that the sender is the expected Jetton wallet contract. Any attacker can send a fake `transfer_notification` message claiming to have transferred tokens, crediting themselves without actually depositing any Jettons.
**Vulnerable Code**:
```func
// staking.fc, line 85
if (op == op::transfer_notification) {
int jetton_amount = in_msg_body~load_coins();
slice from_user = in_msg_body~load_msg_addr();
;; WRONG: No validation of sender_address!
;; Attacker can claim any jetton_amount
credit_user(from_user, jetton_amount);
}
```
**Attack Scenario**:
1. Attacker deploys malicious contract
2. Malicious contract sends `transfer_notification` message to staking contract
3. Message claims attacker transferred 1,000,000 Jettons
4. Staking contract credits attacker without checking sender
5. Attacker can now withdraw from contract or gain benefits without depositing
**Proof of Concept**:
```typescript
// Attacker sends fake transfer_notification
const attackerContract = await blockchain.treasury("attacker");
await stakingContract.sendInternalMessage(attackerContract.getSender(), {
op: OP_CODES.TRANSFER_NOTIFICATION,
jettonAmount: toNano("1000000"), // Fake amount
fromUser: attackerContract.address,
});
// Attacker successfully credited without sending real Jettons
const balance = await stakingContract.getUserBalance(attackerContract.address);
expect(balance).toEqual(toNano("1000000")); // Attack succeeded
```
**Recommendation**:
Store expected Jetton wallet address and validate sender:
```func
global slice jetton_wallet_address;
() recv_internal(...) impure {
load_data(); ;; Load jetton_wallet_address from storage
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
slice sender_address = cs~load_msg_addr();
int op = in_msg_body~load_uint(32);
if (op == op::transfer_notification) {
;; CRITICAL: Validate sender
throw_unless(error::wrong_jetton_wallet,
equal_slices(sender_address, jetton_wallet_address));
int jetton_amount = in_msg_body~load_coins();
slice from_user = in_msg_body~load_msg_addr();
;; Safe to credit user
credit_user(from_user, jetton_amount);
}
}
```
**References**:
- building-secure-contracts/not-so-smart-contracts/ton/fake_jetton_contract
9. 優先度のガイドライン
Critical(即時修正が必要)
- 偽の Jetton コントラクト(不正なミント/クレジット付与)
High(ローンチ前に修正)
- 整数をブール値として使用(ロジックエラー、条件の誤動作)
- ガスチェックなしで TON を転送(残高の枯渇)
10. テストに関する推奨事項
単体テスト
import { Blockchain } from "@ton/sandbox";
import { toNano } from "ton-core";
describe("Security tests", () => {
let blockchain: Blockchain;
let contract: Contract;
beforeEach(async () => {
blockchain = await Blockchain.create();
contract = blockchain.openContract(await Contract.fromInit());
});
it("should use correct boolean values", async () => {
// Test that TRUE = -1, FALSE = 0
const result = await contract.getFlag();
expect(result).toEqual(-1n); // True
expect(result).not.toEqual(1n); // Not 1!
});
it("should reject fake jetton transfer", async () => {
const attacker = await blockchain.treasury("attacker");
const result = await contract.send(
attacker.getSender(),
{ value: toNano("0.05") },
{
$$type: "TransferNotification",
query_id: 0n,
amount: toNano("1000"),
from: attacker.address,
}
);
expect(result.transactions).toHaveTransaction({
success: false, // Should reject
});
});
it("should validate gas for forward amount", async () => {
const result = await contract.send(
user.getSender(),
{ value: toNano("0.01") }, // Insufficient gas
{
$$type: "Transfer",
to: recipient.address,
forward_ton_amount: toNano("1"), // Trying to forward 1 TON
}
);
expect(result.transactions).toHaveTransaction({
success: false,
});
});
});
統合テスト
// Test with real Jetton wallet
it("should accept transfer from real jetton wallet", async () => {
// Deploy actual Jetton minter and wallet
const jettonMinter = await blockchain.openContract(JettonMinter.create());
const userJettonWallet = await jettonMinter.getWalletAddress(user.address);
// Set jetton wallet in contract
await contract.setJettonWallet(userJettonWallet);
// Real transfer from Jetton wallet
const result = await userJettonWallet.sendTransfer(
user.getSender(),
contract.address,
toNano("100"),
{}
);
expect(result.transactions).toHaveTransaction({
to: contract.address,
success: true,
});
});
11. 追加リソース
- 安全なコントラクトの構築:
building-secure-contracts/not-so-smart-contracts/ton/ - TON ドキュメント: https://docs.ton.org/
- FunC ドキュメント: https://docs.ton.org/develop/func/overview
- TON Blueprint: https://github.com/ton-org/blueprint
- Jetton 標準: https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md
12. クイックリファレンスチェックリスト
TON コントラクトの監査を完了する前に確認してください。
ブール値のロジック(HIGH):
- すべてのブール値で、true に -1、false に 0 を使用している
- ブール値として正の整数(1、2 など)を使用していない
- ブール値を返す関数が、true の場合に -1 を返す
-
~、&、|を使ったブール値のロジックが正しい値を使用している - テストでブール演算が正しく動作することを確認している
Jetton のセキュリティ(CRITICAL):
-
transfer_notificationハンドラーが送信者アドレスを検証する - 送信者を保存済みの Jetton ウォレットアドレスと照合する
- 初期化時に Jetton ウォレットアドレスを保存する
- Jetton ウォレットを設定/更新する管理者関数がある
- 送信者を検証せずに
forward_payloadを信頼しない - 偽の Jetton コントラクトを使ったテストで拒否されることを確認する
ガスと転送額(HIGH):
- TON の転送額が固定または上限付きである
- または、ユーザー指定の金額を検証する:
msg_value >= tx_fee + forward_amount - コントラクト残高が枯渇しないよう保護されている
- 適切な
send_raw_messageフラグを使用している - 過大な転送額でコントラクトの残高を枯渇させられないことをテストで確認する
テスト:
- 3 種類すべての脆弱性に対する単体テストがある
- 実際の Jetton コントラクトを使った統合テストがある
- すべての操作についてガスコストを分析している
- メインネットの前にテストネットへデプロイしている
- 3 行すべてを含むカバレッジ表を出力し、各行に
found、clear、n/aのいずれかの判定と理由を記載している
13. 却下すべき合理化
- 「コントラクトは小さいので、ほとんどのパターンは明らかに当てはまりません。」 誰にとって明らかなのでしょうか。
n/aと記載するだけで、1 つの節で判断をレビュー可能にできます。何も書かなければ記録は残らず、確認しなかった場合と区別できません。パターンが 3 つしかないため、この調査が完了できないほど大規模になることはありません。 - 「FunC 用の自動ツールがないので、体系的なカバレッジは実現できません。」 スキャナーがないことは、表が重要である理由であり、確認を省略する口実ではありません。ここでの方法は手動レビューであり、表によってそのレビューを監査可能にします。
- 「このコントラクトに関係するパターンは確認しました。」 どのパターンが関係するかを判断すること自体が調査であり、調査を始める前提条件ではありません。行を省くのではなく、表を完成させてから深刻度で順位付けしてください。
- 「指摘事項はないので、報告することはありません。」 指摘事項がゼロの調査でも、カバレッジ表全体を出力します。その表が成果物です。コントラクトをきちんと調査したのか、ざっと目を通しただけなのかを区別できます。
- 「送信元は明らかに Jetton ウォレットです。」 なら、照合対象となる保存済みアドレスを示してください。どのコントラクトも転送通知を送信できます。確認するのは、コントラクト自身が計算したアドレスとの照合であり、それがないことが偽 Jetton のバグです。
- 「メッセージは非バウンス可能なので、ガスは問題ありません。」 リザーブを明示してください。残りの実行に十分な量を残さずに転送すると、バウンスの挙動にかかわらず、処理の途中でコントラクトが行き詰まります。