mcpField
T
ton-vulnerability-scanner

ton-vulnerability-scanner

github.com/trailofbits安全7.9k5.2k 次安装今天更新
代码仓库

关于 ton-vulnerability-scanner

ton-vulnerability-scanner 是面向 TON FunC 智能合约的安全审查 Skill,检查三类 TON 特有风险,并报告覆盖情况、严重性、文件位置和修复建议。

主要功能

  1. 01检查将整数用作布尔值的逻辑
  2. 02审查 Jetton 通知发送方验证
  3. 03检查 TON 转发与 Gas 处理
  4. 04逐项报告三类模式的判定
  5. 05提供问题位置与修复建议

适用场景

  1. 01上线前审计 FunC 合约
  2. 02审查 Jetton 转账处理器
  3. 03检查 TON 转发与 Gas 逻辑

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/ton-vulnerability-scanner确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
assets/
trail-of-bits-mark.svg
resources/
VULNERABILITY_PATTERNS.md

详细介绍

TON 漏洞扫描器

1. 用途

系统地扫描使用 FunC 编写的 TON 区块链智能合约,查找与布尔逻辑、Jetton 代币处理和 Gas 管理相关的平台特定安全漏洞。此技能包含 3 种 TON 架构独有的关键漏洞模式。

2. 何时使用此技能

  • 审计 TON 智能合约(FunC 语言)
  • 审查 Jetton 代币实现
  • 验证代币转账通知处理程序
  • 对 TON dApp 进行上线前安全评估
  • 审查 Gas 转发逻辑
  • 评估布尔条件处理

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. 此技能的工作方式

调用后,我将:

  1. 搜索你的代码库,查找 FunC/Tact 合约
  2. 分析每个合约,检查 3 种漏洞模式
  3. 报告发现,附上文件引用和严重程度,并在报告前提供一张覆盖情况表,为每种模式标注判定结果
  4. 提供修复方案,针对每个已识别的问题
  5. 输出覆盖情况表——包含全部 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。

模式摘要:
  1. 将整数用作布尔值 ⚠️ HIGH - 使用正整数表示真;FunC 中的真值为 -1
  2. 伪造的 Jetton 合约 ⚠️ CRITICAL - 未验证 transfer_notification 的发送方
  3. 转发 TON 时未检查 Gas ⚠️ HIGH - 转发前未预留剩余执行所需的 Gas

有关包含代码示例的完整漏洞模式,请参阅 VULNERABILITY_PATTERNS.md。

7. 扫描工作流

步骤 1:平台识别
  1. 确认使用 FunC 语言(存在 .fc 或 .func 文件)
  2. 检查项目结构是否符合 TON Blueprint 或 toncli
  3. 定位合约源文件
  4. 识别与 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/

对于每个布尔值:

  • 使用 -1 表示真,0 表示假
  • 未使用 1 或其他正整数
  • 逻辑运算正常工作
步骤 3:Jetton 处理程序分析
# Find transfer_notification handlers
rg "transfer_notification|op::transfer_notification" contracts/

对于每个 Jetton 处理程序:

  • 验证发送方地址
  • 将发送方与存储的 Jetton 钱包地址进行比对
  • 未验证发送方时,不信任 forward_payload
  • 提供用于设置 Jetton 钱包地址的管理员函数
步骤 4:Gas/转发金额审查
# 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 合约需要进行全面的人工审查:

  • 使用 ~、&、| 运算符的布尔逻辑
  • 消息解析和验证
  • Gas 经济机制和费用计算
  • 存储操作和数据序列化

8. 报告格式

覆盖情况表

报告第 6 节中的每种模式,无论是否发现问题。请将此表放在发现项之前,并包含全部 3 行:

#模式结论证据
1整数用作布尔值clear搜索了 is_/has_/flag;所有值都设为 -1
2伪造 Jetton 合约
3未检查 Gas 就转发 TON

每项结论只能是以下之一:

  • found — 引用 file:line,并在下方完整写出发现项。
  • clear — 该模式适用于此合约,且合约已妥善处理。请说明你搜索的函数、存储的地址或检查项,以便读者重复搜索。
  • n/a — 此模式不适用于此处。用一个分句说明原因(“此合约不处理任何 Jetton 转账通知”)。未检查不属于 n/a。

这份清单只有三种模式,因此表格不完整更难以辩解,不能因此降低要求:报告若只覆盖一种模式,却对另外两种保持沉默,看起来就像合约没有问题。Verdict 单元格为空也同样属于不完整:上方第 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. 优先级指南

严重(需要立即修复)
  • 伪造 Jetton 合约(未经授权的铸造/记账)
高(上线前修复)
  • 整数用作布尔值(逻辑错误、条件失效)
  • 未检查 Gas 就转发 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. 其他资源


12. 快速参考清单

完成 TON 合约审计前:

布尔逻辑(高):

  • 所有布尔值都使用 -1(真)和 0(假)
  • 不使用正整数(1、2 等)作为布尔值
  • 返回布尔值的函数在真时返回 -1
  • 使用 ~、&、| 的布尔逻辑采用正确的值
  • 测试验证布尔运算正常工作

Jetton 安全(严重):

  • transfer_notification 处理函数会验证发送者地址
  • 将发送者与存储的 Jetton 钱包地址进行核对
  • 初始化时存储 Jetton 钱包地址
  • 提供用于设置/更新 Jetton 钱包的管理员函数
  • 未验证发送者前,不能信任 forward_payload
  • 使用伪造 Jetton 合约进行测试,确认其会被拒绝

Gas 和转发金额(高):

  • 转发的 TON 金额是固定值/有上限
  • 或者验证用户提供的金额:msg_value >= tx_fee + forward_amount
  • 防止合约余额被耗尽
  • 使用适当的 send_raw_message 标志
  • 测试验证无法通过过高的转发金额耗尽合约资金

测试:

  • 对全部三种漏洞类型进行单元测试
  • 使用真实 Jetton 合约进行集成测试
  • 分析所有操作的 Gas 成本
  • 在部署到主网前先部署到测试网
  • 输出覆盖情况表,包含全部 3 行;每行都注明 found、clear 或 n/a,并附上原因

13. 应当拒绝的合理化说法

  • “合约很小,所以大多数模式显然不适用。” 对谁来说显然?写一句 n/a 就能让判断变得可审查。保持沉默不会留下任何记录,读者也无法判断你是检查过了,还是根本没检查。只有 3 种模式,不存在无法完成的扫描。
  • “FunC 没有自动化工具,所以无法系统地覆盖这些模式。” 没有扫描器,正是这张表重要的原因,而不是跳过它的借口。这里采用人工审查;这张表让审查过程可审计。
  • “我检查了这个合约中重要的模式。” 判断哪些模式重要,本身就是扫描的一部分,而不是开始扫描的前提。应当在填完整张表后再按严重程度排序,而不是直接漏掉某些行。
  • “没有发现问题,所以没什么可报告的。” 即使扫描未发现问题,也仍应提供完整的覆盖表。这张表就是交付成果:它能区分经过检查的合约和只是被粗略看过的合约。
  • “发送方显然就是 Jetton 钱包。” 那就引用用于比较的存储地址。任何合约都能发送转账通知;检查的关键是与合约自身计算出的地址进行比较。缺少这项检查,就会产生假 Jetton 漏洞。
  • “消息不可退回,所以 gas 不重要。” 说明预留了多少 gas。转发后若没有为剩余执行留足 gas,无论消息是否可退回,合约都会在操作中途卡住。

评论

支持 Markdown