mcpField
C
cairo-vulnerability-scanner

cairo-vulnerability-scanner

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

关于 cairo-vulnerability-scanner

此技能为 Cairo/StarkNet 合约审计提供扫描流程,针对算术、跨层消息和签名等 6 类漏洞检查代码并给出证据与修复建议。适用于审计人员和发布前的安全审查。

主要功能

  1. 01检查 6 类 Cairo/StarkNet 漏洞
  2. 02分析 L1-L2 消息与地址校验
  3. 03审查签名重放保护
  4. 04按模式输出覆盖表与证据
  5. 05提供漏洞修复建议

适用场景

  1. 01审计 StarkNet 智能合约
  2. 02检查 L1-L2 桥接实现
  3. 03发布前审查签名验证逻辑

安装

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

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

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

包含的文件

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

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

详细介绍

Cairo/StarkNet 漏洞扫描器

1. 用途

系统地扫描 StarkNet 上的 Cairo 智能合约,查找与算术、跨层消息传递和密码学操作相关的平台特定安全漏洞。此技能涵盖 Cairo/StarkNet 生态系统特有的 6 种关键漏洞模式。

2. 何时使用此技能

  • 审计 StarkNet 智能合约(Cairo)
  • 审查 L1-L2 桥接实现
  • 在 StarkNet 应用上线前进行安全评估
  • 验证跨层消息处理
  • 审查签名验证逻辑
  • 评估 L1 handler 函数

3. 平台检测

文件扩展名和标识
  • Cairo 文件:.cairo
语言/框架标记
// Cairo contract indicators
#[contract]
mod MyContract {
    use starknet::ContractAddress;

    #[storage]
    struct Storage {
        balance: LegacyMap<ContractAddress, felt252>,
    }

    #[external(v0)]
    fn transfer(ref self: ContractState, to: ContractAddress, amount: felt252) {
        // Contract logic
    }

    #[l1_handler]
    fn handle_deposit(ref self: ContractState, from_address: felt252, amount: u256) {
        // L1 message handler
    }
}

// Common patterns
felt252, u128, u256
ContractAddress, EthAddress
#[external(v0)], #[l1_handler], #[constructor]
get_caller_address(), get_contract_address()
send_message_to_l1_syscall
项目结构
  • src/contract.cairo - 主合约实现
  • src/lib.cairo - 库模块
  • tests/ - 合约测试
  • Scarb.toml - Cairo 项目配置
工具支持
  • Caracal:Trail of Bits 提供的 Cairo 静态分析器
  • 安装:cargo install --git https://github.com/crytic/caracal --profile release --force(这是 Rust 工具,不在 PyPI 上)
  • 用法:caracal detect src/
  • cairo-test:内置测试框架
  • Starknet Foundry:测试和开发工具包

4. 此技能的工作方式

调用后,我将:

  1. 搜索你的代码库,查找 Cairo 文件
  2. 分析每个合约,检查 6 种漏洞模式
  3. 报告发现的问题,附上文件引用和严重程度,并在其上方提供一张涵盖所有模式判定结果的覆盖率表
  4. 为每个已识别的问题提供修复方案
  5. 检查 L1-L2 交互,查找消息传递漏洞

5. 输出示例

发现漏洞时,你会收到类似这样的报告:

=== CAIRO/STARKNET VULNERABILITY SCAN RESULTS ===

6. 漏洞模式(6 种模式)

我会检查 Cairo/StarkNet 特有的 6 种关键漏洞模式。有关详细的检测模式、代码示例、缓解措施和测试策略,请参阅 VULNERABILITY_PATTERNS.md。

模式摘要:
  1. Felt252 算术溢出/下溢 ⚠️ HIGH - felt252 会静默回绕;请使用 u128/u256
  2. L1 到 L2 地址转换 ⚠️ HIGH - 未验证 L1 地址是否小于 STARKNET_FIELD_PRIME
  3. L1 到 L2 消息失败 ⚠️ HIGH - 消息无法消费时没有取消路径
  4. 过度约束的 L1 <-> L2 交互 ⚠️ MEDIUM - 可能导致资金被困或阻碍进展的耦合
  5. 签名重放保护 ⚠️ HIGH - 没有 nonce,或域分隔符缺少链 ID/合约地址
  6. 未检查 L1 Handler 中的 from_address ⚠️ CRITICAL - 任何 L1 合约都能驱动该 handler

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

7. 扫描工作流

步骤 1:平台识别
  1. 确认使用 Cairo 语言和 StarkNet 框架
  2. 检查 Cairo 版本(Cairo 1.0+ 或旧版 Cairo 0)
  3. 定位合约文件(src/*.cairo)
  4. 识别 L1-L2 桥接合约(如适用)
步骤 2:算术安全扫描
# Find felt252 usage in arithmetic
rg "felt252" src/ | rg "[-+*/]"

# Find balance/amount storage using felt252
rg "felt252" src/ | rg "balance|amount|total|supply"

# Should prefer u128, u256 instead
步骤 3:L1 Handler 分析

对每个 #[l1_handler] 函数:

  • 验证 from_address 参数
  • 检查地址是否不等于零
  • 是否具有适当的访问控制
  • 是否为监控发出事件
步骤 4:签名验证审查

对于基于签名的函数:

  • 包含 nonce 跟踪
  • 使用后递增 nonce
  • 域分隔符包含链 ID 和合约地址
  • 无法重放签名
步骤 5:L1-L2 桥接审计

如果合约包含桥接功能:

  • L1 验证地址 < STARKNET_FIELD_PRIME
  • L1 实现消息取消机制
  • L2 在 handler 中验证 from_address
  • L1 ↔ L2 具有对称的访问控制
  • 测试完整的往返流程
步骤 6:使用 Caracal 进行静态分析
# Run Caracal detectors
caracal detect src/

# Specific detectors
caracal detect src/ --detectors unchecked-felt252-arithmetic
caracal detect src/ --detectors unchecked-l1-handler-from
caracal detect src/ --detectors missing-nonce-validation

8. 报告格式

覆盖情况表

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

#模式判定证据
1Felt252 算术溢出/下溢clear余额使用 u256;已搜索算术运算中的 felt252,未发现
2L1 到 L2 地址转换
3L1 到 L2 消息失败
4约束过严的 L1 <-> L2 交互
5签名重放保护
6L1 处理函数中未检查的 from_address

每项判定必须是以下之一:

  • found —— 引用 file:line,并在下方完整写出发现项。
  • clear —— 此模式适用于该合约,且合约已处理此问题。注明你搜索的函数、trait 或检查项,方便读者复查。
  • n/a —— 此模式不适用于此处。用一个分句说明原因(“此合约没有 L1 处理函数”)。未进行检查不能判为 n/a。

少于 6 行的表格表示扫描不完整,必须报告为不完整。判定单元格为空的行也同样表示不完整:上方已填写第 1 行以展示格式,报告完成前必须以相同方式填写每一行。6 项均判定为 clear 也是读者可以据此采取行动的结果。只涵盖两种模式而对其余四种只字不提的报告,看起来和一份没有问题的合约完全一样,而这正是此表旨在防止的问题。

发现项模板
## [CRITICAL] Unchecked from_address in L1 Handler

**Location**: `src/bridge.cairo:145-155` (handle_deposit function)

**Description**:
The `handle_deposit` L1 handler function does not validate the `from_address` parameter. Any L1 contract can send messages to this function and mint tokens for arbitrary users, bypassing the intended L1 bridge access controls.

**Vulnerable Code**:
```rust
// bridge.cairo, line 145
#[l1_handler]
fn handle_deposit(
    ref self: ContractState,
    from_address: felt252,  // Not validated!
    user: ContractAddress,
    amount: u256
) {
    let current_balance = self.balances.read(user);
    self.balances.write(user, current_balance + amount);
}
```

**Attack Scenario**:
1. Attacker deploys malicious L1 contract
2. Malicious contract calls `starknetCore.sendMessageToL2(l2Contract, selector, [attacker_address, 1000000])`
3. L2 handler processes message without checking sender
4. Attacker receives 1,000,000 tokens without depositing any funds
5. Protocol suffers infinite mint vulnerability

**Recommendation**:
Validate `from_address` against authorized L1 bridge:
```rust
#[l1_handler]
fn handle_deposit(
    ref self: ContractState,
    from_address: felt252,
    user: ContractAddress,
    amount: u256
) {
    // Validate L1 sender
    let authorized_l1_bridge = self.l1_bridge_address.read();
    assert(from_address == authorized_l1_bridge, 'Unauthorized L1 sender');

    let current_balance = self.balances.read(user);
    self.balances.write(user, current_balance + amount);
}
```

**References**:
- building-secure-contracts/not-so-smart-contracts/cairo/unchecked_l1_handler_from
- Caracal detector: `unchecked-l1-handler-from`

9. 优先级指南

严重(需要立即修复)
  • L1 处理函数中未检查的 from_address(无限铸币)
  • L1-L2 地址转换问题(资金转入零地址)
高(部署前修复)
  • Felt252 算术溢出/下溢(余额操纵)
  • 缺少签名重放保护(重放攻击)
  • L1-L2 消息失败且无法取消(资金被锁定)
中(审计时处理)
  • 约束过严的 L1-L2 交互(资金被困)

10. 测试建议

单元测试
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_felt252_overflow() {
        // Test arithmetic edge cases
    }

    #[test]
    #[should_panic]
    fn test_unauthorized_l1_handler() {
        // Wrong from_address should fail
    }

    #[test]
    fn test_signature_replay_protection() {
        // Same signature twice should fail
    }
}
集成测试(与 L1 配合)
// Test full L1-L2 flow
#[test]
fn test_deposit_withdraw_roundtrip() {
    // 1. Deposit on L1
    // 2. Wait for L2 processing
    // 3. Verify L2 balance
    // 4. Withdraw to L1
    // 5. Verify L1 balance restored
}
Caracal CI 集成
# .github/workflows/security.yml
- name: Run Caracal
  run: |
    # Rebuilds from source each run; cache ~/.cargo or pin a release binary instead.
    cargo install --git https://github.com/crytic/caracal --profile release --force
    caracal detect src/ --fail-on high,critical

11. 其他资源


12. 快速参考清单

完成 Cairo/StarkNet 审计前:

算术安全(高):

  • 不使用 felt252 表示余额/金额(使用 u128/u256)
  • 或者 felt252 算术有明确的边界检查
  • 已测试溢出/下溢场景

L1 处理函数安全(严重):

  • 所有 #[l1_handler] 函数都验证 from_address
  • 将 from_address 与存储的 L1 合约地址进行比较
  • 无法通过部署其他 L1 合约绕过检查

L1-L2 消息传递(高):

  • L1 桥验证地址 < STARKNET_FIELD_PRIME
  • L1 桥实现消息取消功能
  • L2 处理函数检查 from_address
  • L1 ↔ L2 验证规则对称
  • 已测试完整往返流程

签名安全(高):

  • 签名包含 nonce 跟踪
  • 每次使用后递增 nonce
  • 域分隔符包含链 ID 和合约地址
  • 已测试并阻止签名重放
  • 已阻止跨链重放

工具使用:

  • 已完成 Caracal 扫描,且没有严重发现
  • 单元测试涵盖所有漏洞场景
  • 集成测试验证 L1-L2 流程
  • 主网部署前已在测试网上测试
  • 已输出覆盖情况表,包含全部 6 行,每行均标注 found、clear 或 n/a,并说明原因

13. 应拒绝的自我合理化

  • “合约很小,所以大多数模式显然不适用。” 对谁来说显然?写一句 n/a 就能记录判断,便于审查。沉默不会留下任何记录,读者也无法区分你是检查过,还是根本没检查。
  • “Caracal 没有报告问题,所以合约没有问题。” Caracal 只覆盖这 6 种模式中的一部分,完全无法检查逻辑层面的模式。工具运行结果干净,只是一条证据,并不代表对它从未检查过的模式做出了结论。请说明它覆盖了哪些模式。
  • “我检查了这个合约中重要的模式。” 判断哪些模式重要,本身就是扫描的一部分,而不是开始扫描的前提。应在完成表格后再按严重性排序,而不是漏掉某些行。
  • “没有发现问题,所以没什么可报告的。” 即使扫描结果为零,也应输出完整的覆盖表。该表就是交付物:它能区分经过检查的合约和只被粗略看过的合约。
  • “Cairo 1 原生提供溢出检查,所以算术运算是安全的。” 请明确类型。felt252 的行为与定宽整数类型不同,而两者之间的边界正是模式 4 涉及的地方。
  • “L1 端会验证这一点。” 那就引用 L1 合约。你认为桥接两端存在的检查,在亲自阅读之前都只是假设;未经验证的 from_address 是 StarkNet 桥接漏洞的典型例子。

评论

支持 Markdown