返回 SC 笔记
SC Day 55

复习 - Week 9 总结 (漏洞分类 + Move基础)

### 1. Solidity 漏洞分类总表

2026-05-25
第三阶段:安全审计
reviewvulnerability-classificationmove-reviewsecurity-checklistquiz

日期: 2026-05-25 方向: Solidity / Move/Sui 阶段: 第三阶段:安全审计 标签: #review #vulnerability-classification #move-review #security-checklist #quiz


今日目标

  1. 系统化回顾本周学习的 Solidity 安全漏洞(Day 51-53)
  2. 系统化回顾 Move/Sui 基础概念(Day 52, 54)
  3. 建立漏洞分类表和安全检查清单 v1
  4. 通过自测题验证掌握程度

核心概念

1. Solidity 漏洞分类总表

本周覆盖了以下漏洞类别:

分类漏洞名称严重程度关联 EthernautSWC 编号
算术整数溢出/下溢#5 TokenSWC-101
权限tx.origin 钓鱼#4 TelephoneSWC-115
权限缺失访问控制严重-SWC-105
权限未初始化代理严重-SWC-109
特性selfdestruct 强制发 ETH#7 ForceSWC-132
存储存储碰撞(Storage Collision)严重#16 PreservationSWC-112
执行不安全的 delegatecall严重#6 DelegationSWC-112

漏洞按攻击面分类

                    智能合约漏洞分类
                         │
          ┌──────────────┼──────────────┐
          │              │              │
     数据层漏洞      逻辑层漏洞      环境层漏洞
          │              │              │
   ┌──────┴──────┐  ┌───┴───┐    ┌────┴────┐
   │             │  │       │    │         │
整数溢出   存储碰撞  权限缺失  重入   tx.origin  selfdestruct
(SWC-101) (SWC-112) (SWC-105)(SWC-107)(SWC-115) (SWC-132)

2. 每个漏洞的核心要素速查

整数溢出/下溢 (Integer Overflow/Underflow)

触发条件: Solidity < 0.8.0 或 unchecked 块内
攻击效果: 余额/数量变为极大值或0
检测方法: 检查 pragma 版本 + unchecked 使用
防护手段: Solidity 0.8+ / SafeMath / 显式检查
真实案例: batchOverflow (2018) — BEC Token

tx.origin 钓鱼

触发条件: 使用 tx.origin 做权限验证
攻击效果: 攻击者冒充 owner 执行操作
检测方法: 搜索代码中的 tx.origin
防护手段: 用 msg.sender 替代 tx.origin
真实案例: 多个合约审计中发现

selfdestruct 强制发 ETH

触发条件: 合约依赖 address(this).balance 做逻辑
攻击效果: 打破余额假设,DoS 或逻辑错误
检测方法: 搜索 address(this).balance 的使用
防护手段: 使用内部变量追踪金额
注意事项: EIP-6780 后只有同交易内的 selfdestruct 会删除代码

存储碰撞

触发条件: delegatecall 目标的存储布局与调用者不匹配
攻击效果: 覆盖关键变量(如 owner、implementation 地址)
检测方法: 对比 Proxy 和 Implementation 的存储布局
防护手段: EIP-1967 标准 slot / 严格布局匹配
真实案例: Audius 治理攻击 (2022)

未初始化代理

触发条件: Implementation 合约未禁用初始化
攻击效果: 攻击者成为 Implementation 的 owner
检测方法: 检查 Implementation 的构造函数和初始化状态
防护手段: 构造函数中 _disableInitializers()
真实案例: Parity Wallet (2017) — 1.5亿美元锁定

缺失访问控制

触发条件: 敏感函数缺少 onlyOwner/onlyRole 修饰符
攻击效果: 任何人可执行管理操作
检测方法: 审查所有 public/external 函数的权限
防护手段: OpenZeppelin Ownable / AccessControl
真实案例: 各种 Rug Pull 中常见

3. Move/Sui 基础概念回顾

3.1 Move 语言核心

Move 核心三件事:
1. 资源安全 — struct 通过 abilities 控制复制/丢弃
2. 模块封装 — struct 字段只在定义模块内可访问
3. 类型线性 — 每个值有明确的生命周期

3.2 Abilities 速查表

Ability效果缺失时
copy可以复制值值是唯一的(资产)
drop可以丢弃值必须显式消费/转移
store可以存入其他结构不能作为字段被包含
key可以作为顶层存储对象不能独立存在于存储中

常见 Abilities 组合

组合典型用途示例
key, store可转移的资产NFT, Token, 配置
copy, drop, store纯数据类型坐标, 元数据
copy, drop临时值事件, 错误信息
drop一次性 witnessOTW, 初始化证明
(无)热土豆闪电贷凭证

3.3 Sui 对象模型回顾

Sui 对象类型:
┌─────────────┬──────────────┬──────────────┐
│ Owned       │ Shared       │ Immutable    │
├─────────────┼──────────────┼──────────────┤
│ 单一 owner  │ 无 owner     │ 无 owner     │
│ 可修改      │ 可修改       │ 不可修改     │
│ 可转移      │ 不可转移     │ 不可转移     │
│ 无需共识    │ 需要共识     │ 无需共识     │
│ ~400ms      │ ~2s          │ ~400ms       │
│             │              │              │
│ transfer()  │ share_object│ freeze_object│
└─────────────┴──────────────┴──────────────┘

3.4 Sui Move vs Aptos Move 差异总结

特性Aptos MoveSui Move
存储模型全局存储(地址→资源)对象模型(对象 ID)
操作方式move_to / borrow_global函数参数传入
acquires需要声明不需要
并行模型BlockSTM 乐观并行对象级并行
对象类型owned/shared/immutable
包升级支持支持(有限制)

代码实战

安全检查清单 v1 — 实际检查流程

/*
 * 安全审计检查清单 v1
 * 适用于 Solidity 智能合约基础安全审查
 *
 * 使用方法:对每一项执行检查,标记 [PASS] / [FAIL] / [N/A]
 */

// ===== 1. 编译器与版本 =====
// [ ] pragma solidity 版本是否固定(锁定而非浮动)
//     好: pragma solidity 0.8.20;
//     差: pragma solidity ^0.8.0;

// [ ] 是否使用 Solidity 0.8+(内置溢出检查)
// [ ] unchecked 块是否安全(循环计数器等已知安全场景)
// [ ] 类型转换(casting)是否安全(uint256 → uint8 可能截断)

// ===== 2. 访问控制 =====
// [ ] 所有管理函数是否有权限修饰符 (onlyOwner/onlyRole)
// [ ] 是否使用 msg.sender 而非 tx.origin 做权限验证
// [ ] initialize 函数是否有 initializer 修饰符
// [ ] Implementation 构造函数是否调用 _disableInitializers()
// [ ] 函数可见性是否正确(internal/private vs public/external)

// ===== 3. 重入保护 =====
// [ ] 外部调用前是否完成所有状态更新(CEI 模式)
// [ ] 是否使用 ReentrancyGuard (nonReentrant 修饰符)
// [ ] 是否存在跨函数重入风险

// ===== 4. ETH/Token 处理 =====
// [ ] 是否依赖 address(this).balance(selfdestruct 攻击向量)
// [ ] ETH 发送是否使用 call 而非 transfer/send
// [ ] 是否检查 ERC20 transfer/transferFrom 返回值
//     (或使用 SafeERC20)

// ===== 5. 代理/升级 =====
// [ ] 存储布局是否与 proxy 一致
// [ ] 是否使用 EIP-1967 标准 slot
// [ ] 升级是否只追加新变量(不删除/不重排)
// [ ] 是否有升级测试(存储布局兼容性检查)

// ===== 6. 外部交互 =====
// [ ] delegatecall 目标是否受控(不可被用户指定)
// [ ] 外部合约调用是否检查返回值
// [ ] 是否考虑了恶意 ERC20(fee-on-transfer / 重入 token)

综合漏洞检测脚本(概念展示)

"""
简化版漏洞检测脚本(概念展示)
实际审计中应使用 Slither / Mythril / Securify 等专业工具
"""

import re

def check_solidity_file(source_code: str) -> list:
    """基础安全检查"""
    findings = []

    # 检查 1: tx.origin 使用
    if re.search(r'tx\.origin', source_code):
        lines = [i+1 for i, line in enumerate(source_code.split('\n'))
                 if 'tx.origin' in line]
        findings.append({
            'severity': 'HIGH',
            'type': 'tx.origin Usage',
            'description': '使用 tx.origin 可能导致钓鱼攻击',
            'lines': lines,
            'recommendation': '使用 msg.sender 替代 tx.origin'
        })

    # 检查 2: selfdestruct 使用
    if re.search(r'selfdestruct', source_code):
        findings.append({
            'severity': 'MEDIUM',
            'type': 'selfdestruct Usage',
            'description': 'selfdestruct 可能导致资金强制发送',
            'recommendation': '评估是否必要,考虑 EIP-6780 影响'
        })

    # 检查 3: address(this).balance 依赖
    if re.search(r'address\(this\)\.balance', source_code):
        findings.append({
            'severity': 'MEDIUM',
            'type': 'Balance Dependency',
            'description': '依赖合约余额可能被 selfdestruct 操纵',
            'recommendation': '使用内部变量追踪金额'
        })

    # 检查 4: unchecked 块
    unchecked_count = len(re.findall(r'unchecked\s*\{', source_code))
    if unchecked_count > 0:
        findings.append({
            'severity': 'INFO',
            'type': 'Unchecked Arithmetic',
            'description': f'发现 {unchecked_count} 个 unchecked 块',
            'recommendation': '人工验证每个 unchecked 块的安全性'
        })

    # 检查 5: delegatecall 使用
    if re.search(r'\.delegatecall\(', source_code):
        findings.append({
            'severity': 'HIGH',
            'type': 'delegatecall Usage',
            'description': 'delegatecall 可能导致存储碰撞',
            'recommendation': '确保存储布局匹配,目标地址受控'
        })

    # 检查 6: 浮动版本
    if re.search(r'pragma solidity \^', source_code):
        findings.append({
            'severity': 'LOW',
            'type': 'Floating Pragma',
            'description': '使用浮动版本号,可能在不同编译器版本下行为不一致',
            'recommendation': '固定编译器版本,如 pragma solidity 0.8.20;'
        })

    return findings

关键要点总结

本周学习路径

Day 51 (Solidity/Security)     Day 52 (Move/Sui)
├── 整数溢出/下溢               ├── module 声明
├── tx.origin 钓鱼              ├── struct + abilities
├── selfdestruct 攻击           ├── function 类型
└── Ethernaut #5/#4/#7          ├── Sui vs Aptos 差异
                                └── Counter 模块

Day 53 (Solidity/Security)     Day 54 (Move/Sui)
├── 缺失访问控制                ├── owned/shared/immutable
├── 未初始化代理                ├── 对象转移策略
├── 存储碰撞                    ├── Capability Pattern
├── Parity Wallet Hack          ├── NFT 完整模块
└── Ethernaut #16/#6            └── Sui vs EVM 存储对比

安全审计的三个层次

层次方法工具
L1: 自动化扫描静态分析Slither, Mythril, Securify
L2: 人工审查代码审读 + 检查清单审计检查清单
L3: 形式化验证数学证明Certora, Move Prover

Solidity vs Move 安全性对比

漏洞类型SolidityMove
整数溢出0.8+ 内置防护Move 内置防护(abort on overflow)
重入攻击需要 ReentrancyGuard不可能(无 fallback/callback)
存储碰撞Proxy 模式风险不存在(对象模型)
访问控制需要手动实现对象所有权由 runtime 强制
资产复制可能(余额只是数字)不可能(abilities 系统)
资产丢失可能(发送到错地址)类型检查防护

常见误区

误区 1:"通过了自动化扫描就是安全的"

自动化工具只能发现已知模式的漏洞。逻辑漏洞(如激励机制缺陷、经济攻击)需要人工审计。最严重的 DeFi 攻击往往是逻辑问题。

误区 2:"Move 完全没有安全问题"

Move 通过类型系统消除了整类漏洞(重入、溢出、资产复制),但逻辑漏洞仍然存在。例如:

  • 权限设计错误(MintCap 泄露)
  • 经济模型缺陷(价格操纵)
  • 预言机依赖问题
  • 闪电贷攻击(Sui 上也有)

误区 3:"安全检查清单是固定的"

检查清单应该随着新漏洞类型、新 EIP 标准、新攻击手法不断更新。当前清单是 v1,后续会扩展。


自测题

Solidity 安全部分

Q1: 以下代码在 Solidity 0.6.0 中有什么问题?

function withdraw(uint amount) public {
    require(balances[msg.sender] - amount >= 0);
    balances[msg.sender] -= amount;
    payable(msg.sender).transfer(amount);
}
<details> <summary>答案</summary>

整数下溢balances[msg.sender] - amount 如果 amount > balances[msg.sender],结果会下溢为极大值(uint256 总是 >= 0),检查永远通过。

修复:require(balances[msg.sender] >= amount) 或升级到 Solidity 0.8+。

</details>

Q2: 为什么以下权限检查是不安全的?

require(tx.origin == owner);
<details> <summary>答案</summary>

如果攻击者诱骗 owner 调用恶意合约,恶意合约再调用目标合约时,tx.origin 仍然是 owner。应使用 msg.sender == owner

</details>

Q3: 一个合约没有 receive()fallback() 函数,是否能接收 ETH?

<details> <summary>答案</summary>

可以。通过 selfdestruct 强制发送,或作为矿工奖励 coinbase 接收者。因此不能依赖 "合约不能接收 ETH" 的假设。

</details>

Q4: 解释 Ethernaut #16 Preservation 的攻击步骤。

<details> <summary>答案</summary>
  1. 调用 setFirstTime(attackContract)delegatecall 导致 slot 0(即 timeZone1Library)被覆盖为攻击合约地址
  2. 再次调用 setFirstTime(attacker),这次 delegatecall 目标变成了攻击合约
  3. 攻击合约的 setTime 写入 slot 2(对应 owner),覆盖为攻击者地址
</details>

Move/Sui 部分

Q5: 以下 struct 可以被复制吗?可以被丢弃吗?

struct Token has key, store {
    id: UID,
    amount: u64,
}
<details> <summary>答案</summary>

不可以复制(没有 copy ability),不可以丢弃(没有 drop ability)。这意味着 Token 必须被显式转移或解构,保证了资产安全。

</details>

Q6: Sui 中 shared object 和 owned object 的性能差异是什么?

<details> <summary>答案</summary>

Owned object 交易可以跳过共识(fast path),延迟约 400ms。Shared object 需要通过共识排序处理并发写入,延迟约 2 秒。设计时应尽量使用 owned object。

</details>

Q7: 为什么 Move 中不存在重入攻击?

<details> <summary>答案</summary>

Move 没有 fallback/callback 机制。函数调用是同步的、非中断的。当一个函数调用另一个模块的函数时,不存在"执行到一半被回调"的情况。加上资源的线性类型系统,重入攻击在 Move 中从语言层面被消除。

</details>

面试关联

Q: 作为安全审计师,你会如何组织一次智能合约审计?

回答(结构化方法):

  1. 准备阶段:理解项目业务逻辑、阅读文档、确定范围
  2. 自动化扫描:Slither(静态分析)+ Mythril(符号执行)
  3. 人工审查:按检查清单逐项检查,重点关注资金流、权限、外部调用
  4. 逻辑审查:分析经济模型、激励机制、边界条件
  5. 报告撰写:按严重程度分类,提供修复建议和验证方法

Q: Solidity 和 Move 在安全性上有什么根本区别?

回答:Move 在语言层面消除了整类漏洞——资源不可复制/丢弃(消除 token 复制)、无 fallback(消除重入)、对象模型(消除存储碰撞)。Solidity 依赖开发者手动防护这些问题。但两者都面临逻辑漏洞和经济攻击的风险,这些需要人工审计。


参考资源