本文将深入分析本次攻击的原因,并探讨 Sui Move 智能合约的权限控制的特点。
作者:Johan & Lisa
编辑:77
10 月 16 日,Sui 链上的 DeFi 项目 Typus Finance 遭遇黑客攻击,官方已发布攻击事件报告并在报告中致谢慢雾安全团队协助调查和追踪:

本文将深入分析本次攻击的原因,并探讨 Sui Move 智能合约的权限控制的特点。
攻击步骤详解
我们解析第一笔攻击交易:
https://suivision.xyz/txblock/6KJvWtmrZDi5MxUPkJfDNZTLf2DFGKhQA2WuVAdSRUgH
攻击步骤如下:
1 . 篡改价格
相关代码:typus_oracle/sources/oracle.move
public fun update_v2(
oracle: &mut Oracle,
update_authority: & UpdateAuthority,
price: u64,
twap_price: u64,
clock: &Clock,
ctx: &mut TxContext
) {
// check authority
vector::contains(&update_authority.authority, &tx_context::sender(ctx));
version_check(oracle);
update_(oracle, price, twap_price, clock, ctx);
}
//…
fun update_(
oracle: &mut Oracle,
price: u64,
twap_price: u64,
clock: &Clock,
ctx: & TxContext
) {
assert!(price > 0, E_INVALID_PRICE);
assert!(twap_price > 0, E_INVALID_PRICE);
let ts_ms = clock::timestamp_ms(clock);
oracle.price = price;
oracle.twap_price = twap_price;
oracle.ts_ms = ts_ms;
oracle.epoch = tx_context::epoch(ctx);
emit(PriceEvent {id: object::id(oracle), price, ts_ms});
}
我们看一下 update_v2 中的权限验证方式:
传入能力对象 UpdateAuthority,但这个能力对象是一个 share object,意味着它是可以被任何人访问的。
public struct UpdateAuthority has key {
id: UID,
authority: vector,
}
entry fun new_update_authority(
_manager_cap: &ManagerCap,
ctx: &mut TxContext
) {
let update_authority = UpdateAuthority {id: object::new(ctx), authority: vector[ tx_context::sender(ctx) ]};
transfer::share_object(update_authority);
}
开发者的原意是用 authority 列表管理可以访问的用户白名单,但对于返回的结果(是/否)却没有判断 vector::contains(&update_authority.authority,&tx_context::sender(ctx))。
于是攻击者就可以调用 update_v2 更新 oracle.price。
我们看一下 update_v2 中的权限验证方式:
传入能力对象 UpdateAuthority,但这个能力对象是一个 share object,意味着它是可以被任何人访问的。
public struct UpdateAuthority has key { id: UID, authority: vector<address>, } entry fun new_update_authority( _manager_cap: &ManagerCap, ctx: &mut TxContext ) { let update_authority = UpdateAuthority {id: object::new(ctx), authority: vector[ tx_context::sender(ctx) ]}; transfer::share_object(update_authority); }
开发者的原意是用 authority 列表管理可以访问的用户白名单,但对于返回的结果(是/否)却没有判断 vector::contains(&update_authority.authority,&tx_context::sender(ctx))。
于是攻击者就可以调用 update_v2 更新 oracle.price。
Transaction 1-2:更新预言机价格- Oracle 0x0a31...c0d0:价格设为 651,548,270- Oracle 0x6e7c...bc21:价格设为 1
2. 第一次套利:SUI → XBTC
相关代码:typus_perp/sources/tlp/lp_pool.move
public fun swap<F_TOKEN, T_TOKEN>( version: &mut Version, registry: &mut Registry, index: u64, oracle_from_token: &Oracle, oracle_to_token: &Oracle, from_coin: Coin<F_TOKEN>, min_to_amount: u64, clock: &Clock, ctx: &mut TxContext, ): Coin<T_TOKEN> { // ... let (price_f_token_to_usd, price_f_decimal) = oracle_from_token.get_price_with_interval_ms(clock, 0); let (price_t_token_to_usd, price_t_decimal) = oracle_to_token.get_price_with_interval_ms(clock, 0); //... // calculate to_amount_value by oracle price let from_amount_usd = math::amount_to_usd( from_amount, f_token_config.liquidity_token_decimal, price_f_token_to_usd, price_f_decimal ); let to_amount_value = math::usd_to_amount( from_amount_usd, t_token_config.liquidity_token_decimal, price_t_token_to_usd, price_t_decimal ); //... let to_amount_after_fee = math::usd_to_amount( from_amount_usd - fee_amount_usd, t_token_config.liquidity_token_decimal, price_t_token_to_usd, price_t_decimal ); assert!(to_amount_after_fee >= min_to_amount, error::reach_slippage_threshold()); // deposit { let swap_fee_protocol_share_bp = { let token_pool = get_mut_token_pool(liquidity_pool, &f_token_type); token_pool.config.spot_config.swap_fee_protocol_share_bp }; let mut from_balance = from_coin.into_balance(); let protocol_fee_balance = from_balance.split(((fee_amount as u128) * (swap_fee_protocol_share_bp as u128) / 10000 as u64)); let from_balance_value_after_fee = from_balance.value(); admin::charge_fee(version, protocol_fee_balance); balance::join(dynamic_field::borrow_mut(&mut liquidity_pool.id, f_token_type), from_balance); let token_pool = get_mut_token_pool(liquidity_pool, &f_token_type); assert!(token_pool.state.liquidity_amount + from_balance_value_after_fee <= token_pool.config.spot_config.max_capacity, error::reach_max_capacity()); token_pool.state.liquidity_amount = token_pool.state.liquidity_amount + from_balance_value_after_fee; update_tvl(version, liquidity_pool, f_token_type, oracle_from_token, clock); }; // withdraw let to_balance = { let to_balance = balance::split( dynamic_field::borrow_mut<TypeName, Balance<T_TOKEN>>(&mut liquidity_pool.id, t_token_type), to_amount_after_fee ); let withdraw_amount = balance::value(&to_balance); let token_pool = get_mut_token_pool(liquidity_pool, &t_token_type); token_pool.state.liquidity_amount = token_pool.state.liquidity_amount - withdraw_amount; assert!(token_pool.state.liquidity_amount >= token_pool.state.reserved_amount, error::liquidity_not_enough()); update_tvl(version, liquidity_pool, t_token_type, oracle_to_token, clock); to_balance }; emit(SwapEvent { sender: tx_context::sender(ctx), index, from_token_type: f_token_type, from_amount, to_token_type: t_token_type, min_to_amount, actual_to_amount: to_amount_after_fee, fee_amount, fee_amount_usd, oracle_price_from_token: price_f_token_to_usd, oracle_price_to_token: price_t_token_to_usd, u64_padding: vector::empty() }); coin::from_balance(to_balance, ctx) }
攻击者调用 swap 方法兑换代币,由于当前池子的兑换算法并非使用经典的恒定乘积计算代币输出,而是调用 .get_price_with_interval_ms() 方法去取预言机价格,这个价格就是攻击者已经篡改过的价格。
Transaction 3: LP 池交换
- 输入:1 SUI(精度 9)
- 输出:60,000,000 XBTC(精度 8,价值约 0.6 BTC)
如此攻击者就用了少量的代币换取了高价值的代币,接下来只需要如法炮制掏空其它池子。
- 持续攻击套利
从攻击者账号操作记录我们可以看到攻击者共发起了 10 次的 update_v2 攻击,并立即将攻击所得通过跨链桥转移走。
- MistTrack 分析
据链上反洗钱与追踪工具 MistTrack 分析,被盗资产总额约 344 万美元,包括 588,357.9 SUI、1,604,034.7 USDC、0.6 xBTC 和 32.227 suiETH。

SUI 链黑客地址 0xc99ac031ff19e9bff0b5b3f5b870c82402db99c30dfec2d406eb2088be6c2194 先将 suiETH、xBTC 以及大部分 SUI 通过 Cetus、
Haedal Protocol、Turbos 等平台兑换为 USDC。

随后,约 3,430,717 USDC 在 14 笔交易中通过 Circle CCTP 跨链至以太坊黑
客地址 0xeb8a15d28dd54231e7e950f5720bc3d7af77b443。

在以太坊上,这笔资金通过 Curve 兑换为 3,430,241.91 DAI,并转入新地址
0x4502cf2bc9c8743e63cbb9bbde0011989eed03c1,目前该地址上的 DAI 未转出。

追踪发现,以太坊黑客地址
0xeb8a15d28dd54231e7e950f5720bc3d7af77b443 的初始资金来自地址
0x7efcc6f928fb29fddc8dd5f769ff003da590e9eb——该地址此前在 BSC 上通过 Tornado Cash 提取 1 BNB,其中 0.146 BNB 被兑换为 0.041 ETH 并跨链至以太坊。

此外,该以太坊黑客地址还收到来自 SUI 黑客地址的少量资金:共 120.7242 SUI 经由 Mayan Finance 跨链后,转换为约 0.079 ETH 存入,接着该以太坊黑客地址 0xeb8a 将 0.11 ETH 转入地址 0x4502cf2bc9c8743e63cbb9bbde0011989eed03c1。
进一步分析显示,该地址 0x4502 同时还收到来自地址 0x04586599bbe44cb9f0d8faa96567670f93d873e3 转入的 0.1379 ETH,而这 0.1379 ETH 则来自 Mayan Finance 转入——往上追溯,资金来自另一个 SUI 地址 0x840ed9288595e684e42606073d7a7d5aae35ecf1571943d8bf2d67e283db8466。

该 SUI 地址 0x840ed9288595e684e42606073d7a7d5aae35ecf1571943d8bf2d67e283db8466 共收到 207.6 SUI(其中 41.68 SUI 来自 Bybit),SUI 都被兑换为 USDC 并跨链转出。

相关地址已加入 SlowMist AML 的恶意地址库,我们将持续监控相关资金的异动。
防护建议
这是一个典型的预言机价格操纵攻击,黑客利用了共享对象可以任意访问的特点,结合白名单验证漏洞,任意修改预言机价格,然后通过套利获取大量资金。
修复方法很简单,只需要增加一个 assert! 判断:

Sui Move 智能合约的权限控制的特点
与 Solidity 相比,Sui Move 的权限控制更 “内置” 和强制性:Solidity 依赖开发者手动实现 access control(如 Ownable 合约),而 Sui Move 通过语言级机制强制执行,减少人为错误。
在 Sui Move 中,一切数据都以 “对象”(Object) 形式存在,每个对象都有唯一的 ID (Object ID),并绑定到一个所有者 (Owner)。对象不能被复制,只能被 “移动”(Move) 或销毁,这通过 Move 的线性类型系统强制执行。
所有权类型:
地址所有 (Address-Owned):对象属于特定地址,只有该地址的所有者才能修改或转移它。这是最常见的权限控制方式,类似于 “私有财产”,防止他人未授权访问。
共享所有 (Shared):对象可以被任何人访问,但修改需要通过共识机制。这适合 DeFi 或游戏中的共享资产,但会引入轻微的性能开销。
不可变 (Immutable):对象一旦冻结,就无法修改,类似于 “只读” 权限,常用于 NFT 或稳定配置。
包所有 (Package-Owned):对象属于某个模块包,常用于模块内部状态。
这种模型天然防止双重花费和未授权修改,因为资源的所有权是排他的。转移所有权时,使用 transfer 函数明确移动权限。
为了直观说明,我们还是以本次漏洞分析中的 Typus 项目中的安全代码片段,展示正确使用能力控制管理员权限的方法:
相关代码:typus_oracle/sources/oracle.move
fun init(ctx: &mut TxContext) {
transfer::transfer(
ManagerCap {id: object::new(ctx)},
tx_context::sender(ctx)
);
}
//…
public fun update(
oracle: &mut Oracle,
manager_cap: &ManagerCap, price: u64, twap_price: u64, clock: &Clock, ctx: &mut TxContext ) { version_check(oracle); update(oracle, price, twap_price, clock, ctx);
}
在这个例子中,首先 init 初始化时将 ManagerCap 转移给 sender,也就是协议管理员,ManagerCap 作为能力对象,在 update 时需要传入,确保只有管理者能执行敏感操作。
结语
尽管 Sui Move 通过对象所有权模型和能力机制提供了强大的权限控制基础,但开发者仍需在关键函数中正确实现权限验证。这个案例提醒我们:再先进的语言特性也无法完全替代严谨的编码实践和全面的安全审计。
免责声明:作为区块链信息平台,本站所发布文章仅代表作者及嘉宾个人观点,与 Web3Caff 立场无关。文章内的信息仅供参考,均不构成任何投资建议及要约,并请您遵守所在国家或地区的相关法律法规。




