复习 - Week 9 总结 (漏洞分类 + Move基础)
### 1. Solidity 漏洞分类总表
日期: 2026-05-25 方向: Solidity / Move/Sui 阶段: 第三阶段:安全审计 标签: #review #vulnerability-classification #move-review #security-checklist #quiz
今日目标
- 系统化回顾本周学习的 Solidity 安全漏洞(Day 51-53)
- 系统化回顾 Move/Sui 基础概念(Day 52, 54)
- 建立漏洞分类表和安全检查清单 v1
- 通过自测题验证掌握程度
核心概念
1. Solidity 漏洞分类总表
本周覆盖了以下漏洞类别:
| 分类 | 漏洞名称 | 严重程度 | 关联 Ethernaut | SWC 编号 |
|---|---|---|---|---|
| 算术 | 整数溢出/下溢 | 高 | #5 Token | SWC-101 |
| 权限 | tx.origin 钓鱼 | 高 | #4 Telephone | SWC-115 |
| 权限 | 缺失访问控制 | 严重 | - | SWC-105 |
| 权限 | 未初始化代理 | 严重 | - | SWC-109 |
| 特性 | selfdestruct 强制发 ETH | 中 | #7 Force | SWC-132 |
| 存储 | 存储碰撞(Storage Collision) | 严重 | #16 Preservation | SWC-112 |
| 执行 | 不安全的 delegatecall | 严重 | #6 Delegation | SWC-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 | 一次性 witness | OTW, 初始化证明 |
| (无) | 热土豆 | 闪电贷凭证 |
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 Move | Sui 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 安全性对比
| 漏洞类型 | Solidity | Move |
|---|---|---|
| 整数溢出 | 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+。
Q2: 为什么以下权限检查是不安全的?
require(tx.origin == owner);
<details>
<summary>答案</summary>
如果攻击者诱骗 owner 调用恶意合约,恶意合约再调用目标合约时,tx.origin 仍然是 owner。应使用 msg.sender == owner。
Q3: 一个合约没有 receive() 和 fallback() 函数,是否能接收 ETH?
可以。通过 selfdestruct 强制发送,或作为矿工奖励 coinbase 接收者。因此不能依赖 "合约不能接收 ETH" 的假设。
Q4: 解释 Ethernaut #16 Preservation 的攻击步骤。
<details> <summary>答案</summary>- 调用
setFirstTime(attackContract),delegatecall导致slot 0(即timeZone1Library)被覆盖为攻击合约地址 - 再次调用
setFirstTime(attacker),这次 delegatecall 目标变成了攻击合约 - 攻击合约的
setTime写入slot 2(对应owner),覆盖为攻击者地址
Move/Sui 部分
Q5: 以下 struct 可以被复制吗?可以被丢弃吗?
struct Token has key, store {
id: UID,
amount: u64,
}
<details>
<summary>答案</summary>
不可以复制(没有 copy ability),不可以丢弃(没有 drop ability)。这意味着 Token 必须被显式转移或解构,保证了资产安全。
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: 作为安全审计师,你会如何组织一次智能合约审计?
回答(结构化方法):
- 准备阶段:理解项目业务逻辑、阅读文档、确定范围
- 自动化扫描:Slither(静态分析)+ Mythril(符号执行)
- 人工审查:按检查清单逐项检查,重点关注资金流、权限、外部调用
- 逻辑审查:分析经济模型、激励机制、边界条件
- 报告撰写:按严重程度分类,提供修复建议和验证方法
Q: Solidity 和 Move 在安全性上有什么根本区别?
回答:Move 在语言层面消除了整类漏洞——资源不可复制/丢弃(消除 token 复制)、无 fallback(消除重入)、对象模型(消除存储碰撞)。Solidity 依赖开发者手动防护这些问题。但两者都面临逻辑漏洞和经济攻击的风险,这些需要人工审计。
参考资源
- SWC Registry — 智能合约漏洞分类注册表
- Slither — Trail of Bits 静态分析工具
- Mythril — ConsenSys 符号执行工具
- Ethernaut — OpenZeppelin 安全挑战
- Move Book — Move 语言教程
- Sui Documentation — Sui 开发文档
- Rekt News — 安全事件复盘
- DeFi Hack Labs — DeFi 攻击复现