Move语言安全吗?Typus权限验证漏洞

Typus Finance在Sui区块链上遭到黑客攻击,攻击者通过操纵预言机价格,利用LP池的漏洞进行套利,窃取了约344万美元的资产。文章分析了攻击的详细步骤,并探讨了Sui Move智能合约中权限控制的特点,强调了开发者在关键函数中进行适当权限检查的重要性。

作者:Johan & Lisa

编辑:77

10月16日,Sui 区块链上的 DeFi 项目 Typus Finance 遭受了黑客攻击。官方团队发布了一份事件报告,感谢 SlowMist Security Team 在调查和追踪工作中的协助。

( https://medium.com/@TypusFinance/typus-finance-tlp-oracle-exploit-post-mortem-report-response-plan-ce2d0800808b)

本文将深入分析此次攻击背后的原因,并探讨 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 capability object 作为输入,但这个 capability object 是一个 shared 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 list 来管理允许访问的用户的白名单。但是,代码没有检查 vector::contains(&update_authority.authority, &tx_context::sender(ctx)) 的返回结果。

因此,攻击者能够调用 update_v2 来更新 oracle.price。

交易 1–2:更新 Oracle 价格

  • 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 方法来交换 token。由于池的 swap 算法没有使用经典的恒定乘积公式来计算 token 输出,而是调用 .get_price_with_interval_ms() 方法来获取 oracle 价格,而该价格已经被攻击者篡改。

交易 3: LP 池 swap

  • 输入: 1 SUI (精度: 9)
  • 输出: 60,000,000 XBTC (精度: 8, value ≈ 0.6 BTC)

这样,攻击者使用少量的 token 来获取高价值的 token,然后简单地重复同样的伎俩来耗尽其他池。

3. 持续的套利攻击

从攻击者的账户活动记录中,我们可以看到攻击者总共发起了 10 次独立的 update_v2 攻击,并立即通过跨链桥转移了被盗资产。

https://suivision.xyz/account/0xc99ac031ff19e9bff0b5b3f5b870c82402db99c30dfec2d406eb2088be6c2194?tab=Activity

4. MistTrack 分析

根据区块链反洗钱和追踪工具 MistTrack 的数据,被盗资产总额约为 344 万美元,包括 588,357.9 SUI、1,604,034.7 USDC、0.6 xBTC 和 32.227 suiETH。

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

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

在以太坊上,这些资金被兑换成 3,430,241.91 DAI,然后通过 Curve 转移到一个新地址 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 的恶意地址库 中,我们将继续监控相关资金的任何进一步动向。* ](https://miro.medium.com/v2/resize:fit:700/1AgIxUiTozQLRGeI47L4sIQ.png)

Sui Move 智能合约中权限控制的特点

与 Solidity 相比,Sui Move 中的权限控制更像是“内置”的,并通过设计强制执行。虽然 Solidity 依赖于开发人员手动实现访问控制(例如 Ownable contract),但 Sui Move 通过语言级别的机制来强制执行,从而减少人为错误。

在 Sui Move 中,所有数据都以 object 的形式存在,每个 object 都有一个唯一的 Object ID 并绑定到一个所有者。 Object 不能被复制;它们只能被移动 (Move) 或销毁,这一规则受到 Move 的线性类型系统的严格执行。

所有权类型:

  • Address-Owned: 该 object 属于一个特定的地址,只有该地址的所有者才能修改或转移它。 这是最常见的权限控制形式,类似于私有财产,防止他人未经授权的访问。
  • Shared: 任何人都可以访问该 object,但修改必须通过共识机制。 这适用于 DeFi 或游戏应用程序中的共享资产,尽管它会带来轻微的性能开销。
  • Immutable: 一旦 object 被冻结,它就不能再被修改,类似于只读权限。 这种类型通常用于 NFT 或稳定的配置。
  • Package-Owned: 该 object 属于一个特定的模块包,通常用于维护内部模块状态。

由于资源所有权是独占的,因此该模型本身可以防止双重支付和未经授权的修改。 转移所有权时,transfer 函数用于显式地移动权限。

为了更直观地说明这一点,让我们参考本案例中分析的 Typus 项目中的一段安全代码,该代码演示了使用 capability control 来管理管理员权限的正确方法:

  • 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 转移给发送者,该发送者充当协议管理员。 ManagerCap 充当 capability object,必须在更新期间传入,以确保只有管理员才能执行敏感操作。

结论

虽然 Sui Move 通过其 object 所有权模型和 capability 机制为权限控制提供了强大的基础,但开发人员仍然需要在关键函数中实现适当的权限检查。 此案例提醒我们,无论语言功能多么先进,都无法完全取代严格的编码实践和全面的安全审计。

关于 SlowMist

SlowMist 是一家专注于区块链安全的 威胁情报公司,成立于 2018 年 1 月。该公司由一支拥有超过十年网络安全经验的团队创立,旨在成为一支全球力量。 我们的目标是让区块链生态系统对每个人都尽可能安全。 我们现在是一家著名的国际区块链安全公司,曾与 HashKey Exchange、OSL、MEEX、BGE、BTCBOX、Bitget、BHEX.SG、OKX、Binance、HTX、Amber Group、Crypto.com 等多个知名项目合作。

SlowMist 提供多种服务,包括但不限于安全审计、威胁情报、防御部署、安全顾问和其他与安全相关的服务。 我们还提供 AML(反洗钱)软件、MistEye(安全监控)、SlowMist Hacked(加密黑客档案)、FireWall.x(智能合约防火墙)和其他 SaaS 产品。 我们与 Akamai、BitDefender、RC²、TianJi Partners、IPIP 等国内外公司建立了合作伙伴关系。 我们在加密货币犯罪调查方面的大量工作已被国际组织和政府机构引用,包括联合国安全理事会和联合国毒品和犯罪问题办公室。

通过提供针对各个项目定制的全面安全解决方案,我们可以识别风险并防止它们发生。 我们的团队能够发现并发布多个高风险区块链安全漏洞。 通过这样做,我们可以传播意识并提高区块链生态系统中的安全标准。

  • 原文链接: slowmist.medium.com/is-t...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

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