Bluefin 安全问题剖析:构建更安全的去中心化永续交易平台

文摘   2024-05-27 17:23   美国  


本篇技术研报由 MoveBit 研究团队的 Polaris 撰写


TL;DR


近期,BluefinHackenProof 平台举办了有史以来第一次针对 Move 智能合约的审计竞赛!MoveBit 两名安全研究人员积极参与,发现了 1 个高风险、3 个中等风险,以及若干低风险和信息级别的问题。通过深入分析所发现的问题,我们提出了针对每个问题的解决方案和改进建议,以确保 Bluefin 能够提供安全、高效的交易体验。


01

Bluefin 审计竞赛概述


在 2024 年 2 月 13 日,Bluefin[1](Bluefin 是由 Sui 区块链驱动的去中心化永续交易平台)在 Hackenproof[2] 举办了有史以来第一次针对 Move 智能合约的审计竞赛

 

MoveBit 的两名安全研究人员积极参与了此次竞赛,并取得了令人满意的成绩。总体来说,此次审计共揭示了 2 个高风险、3 个中等风险以及若干低风险和信息级别的问题。其中,MoveBit 在审计竞赛中发现了 1 个高风险、3 个中等风险,以及若干低风险和信息级别的问题。


02

Bluefin 简介


Bluefin 是一个面向专业交易者以及首次交易者的去中心化订单簿交易所,它专注于安全性、透明度,并重新定义了使用链上交易平台的用户体验。由 Sui 区块链驱动的 Bluefin 确保用户体验亚秒级完成的交易,并即时反映在他们的用户界面上,为用户提供高性能交易,同时桥接了 Web2 和 Web3 的体验。


Bluefin 将持续利用最优秀的可行技术,推动卓越的性能、安全性和透明度,结合无需钱包的交易体验,得到了 Polychain、SIG、Brevan Howard 等领先公司的支持。


03

Bluefin 安全问题剖析


Bluefin 致力于为用户提供快速且安全的交易体验。然而,在追求这一目标的过程中,也面临着一些潜在的安全隐患和技术挑战。接下来,我们将对 Bluefin 平台中所发现的安全问题进行详细剖析,以便采取相应的改进措施,确保用户资金得到充分保护,从而构建一个更加安全、可信赖的交易环境。


1.Share 操纵


Bluefin 提供了一个额外的 bluefin_vault 模块,允许用户与 vaults 进行交互。其中,deposit_to_vault 函数允许用户向 vaults 质押资金并获得一定数量的 share,withdraw_from_vault 函数允许用户销毁一定数量的 share 以赎回他们的资金。这个模块的设计与以太坊的 ERC4626 模型相似,ERC4626 是一种代币化金库的标准。

 

然而,Bluefin 当前的质押和提取功能存在着 ERC4626 模型中的经典 share 操纵漏洞。让我们看一下 bluefin_vault.deposit_to_vault 函数中关于 share 计算的部分。


如上代码片段 248 行代码所示,用户的 share 数量被计算为:用户质押代币的数量 * 当前 Vault 中总的 share 数量 / Vault 代币的总余额。然后,如下图代码片段 275 行所示,通过 margin_bank 的 deposit_to_bank 函数,将用户的资金充值到 margin_bank 中。


当用户首次进行质押时,vault_total_balance 和 total_shares 都为 0,这时用户获得的 share 数量就等于用户质押的金额,即 shares = amount。这为攻击者操纵 share 提供了可能性。值得注意的是,在这里存在 share 必须大于 0 的检查,这在一定程度上缓解了 share 操纵攻击,但这个防御措施仍然不够。

 

让我们通过一个例子来描述整个 share 操纵的攻击过程(以用户 Alice 和攻击者 Bob 为例):

1) 假设质押的代币是 SUI。最初,攻击者质押 1 个 MIST 以获得 1 个 share。

2) 用户 Alice 尝试将 2 个 SUI 存入合约。如果此时进行质押 ,Alice 将收到 (1 share * 2e9 MIST) / 1 MIST = 2e9 share。

3) 但是在 Alice 交易之前,攻击者 Bob 直接通过 margin_bank::deposit_to_bank 函数转账 1 个 SUI。

4) 导致 Alice 的当前质押 share 计算现在为 (1 share * 2 SUI) / (1 SUI + 1 MIST)。

5) 由于向下截断,计算结果为 1 share。这通过了上面提到的 share 必须大于 0 的检查。

6) 最后,攻击者 Bob 通过 withdraw_from_vault 函数提取  SUI,1 个 share 现在价值约为 1.5 SUI,因为合约持有 3 SUI + 1 MIST,而合约只铸造了 2 个 share。


要解决上述的 share 操纵问题,可以考虑以下几个解决方案:

● 如果收到的 share 或者资金不在滑点容忍范围内,则回滚交易(如果可能,添加滑点保护)。

● 部署者应该将足够的资产存入金库,这样进行 share 操纵攻击的成本就高了。

● 向金库添加虚拟流动性,这样定价就如同金库已部署了足够的资产一样。


2.权限控制不当


Bluefin 在每笔交易中调用 validate_unique_tx 函数来验证交易的唯一性,例如 trade、liquidate、deposit_to_bank、withdraw_from_bank 等。下面是交易 hash 的创建过程,通过 513 行代码片段所示的 object::new(ctx) 来保证随机性:


每笔交易都会传入一个交易 hash 并验证是否重复,如下图代码所示:


validate_unique_tx 函数对于确保系统内每笔交易的 tx_hash 的唯一性起着至关重要的作用,并且依赖于不断增长的 sequencer。然而,该函数缺少权限控制并且是 public 可见的,这意味着任何人都可以传入任意 tx_hash,并存储到 sequencer 中。这可能会导致以下几种问题:

● 有效的资源管理和无限制执行预防对于维护协议的安全性和 gas 效率非常重要。攻击者可以随意向 sequencer 中传输垃圾数据,增加 sequencer 的容量,导致引用该 sequencer 的 gas 消耗越来越高。Sui 在交易及其中使用的数据有一些限制,例如最大大小和使用的对象数量。如果 sequencer 的大小无限增长,容易造成 DoS 攻击,导致交易无法执行,资金无法取出等。

● 恶意攻击者可以通过 front-running 的方式,提前向 sequencer 中传入 tx_hash,导致正常交易无法执行,因为 sequencer 中已经存在该交易 hash。

● 如果前端依赖 tx_hash 来解析交易,那么可能会造成前端交易无法解析等问题。

 

为了防止这种攻击,建议对 validate_unique_tx 函数增加权限控制,或者将其可见性更改为 friend。


3.预言机价格处理问题


1) 预言没有检查是不是最新的价格

在 Bluefin 的 exchange 合约中,如 trade、liquidate、deleverage 等许多函数都会调用 perpetual.update_oracle_price 函数从 Pyth 获取价格,并将新价格更新到永续合约中,以便进行后续计算和验证。


正如我们所知,Pyth[3] 是一个向链上提供实时价格的预言机。在 Sui 上,Pyth 每隔几分钟会更新最新的价格。


如下图所示的第 217 行,协议调用 pyth.get_price_unsafe 函数从 Pyth 获取价格。


我们一起看一下 get_price_unsafe 代码。如下第 379 至 384 行所示,Pyth 的官方明确指出[4],该函数返回的价格不保证是最新的。如果需要获取最新可用的价格,则应使用 get_price_no_older_than 函数,以获取不晚于给定时间的价格。


因此,在协议中使用 pyth.get_price_unsafe 可能会获取到过期的价格,导致后续的计算和验证不准确,从而影响协议的正常运行。


2) 预言机不对价格检查 0 值

同样的,协议在上述从 Pyth 获取价格的过程中,未考虑到价格为 0 的情况。让我们来看一下 Pyth 对价格正负值的处理。由于 Move 不支持原生的负数,Pyth 增加了一个额外的 negative 字段来表示正负值。而 0 值用 (0, false) 表示,即 0 是非负数,也就是说在 Pyth 中 0 价格被视为正数。


当前协议通过 get_magnitude_if_positive 判断获取的价格是否为非负数,但是未考虑到价格为 0 的情况,这意味着我们可以获取到价格为 0 的值。另外,Pyth 也有可能返回价格为 0 的值。参考下图:


为了防止 Pyth 返回价格为 0 值的情况,建议对价格增加非 0 值的检查。


4.危险的单步所有权转移


Bluefin 当前协议中涉及多处权限转移操作,例如更改交易所的管理员权限。管理员在当前协议中扮演关键角色,例如设置交易所的监护人,指定具有一定权限的操作员等。然而,现有的管理员权限转移实现方式存在潜在的安全风险。目前的实现将交易所的管理权直接转移给新地址,如下图所示,这种方法虽然简单,但在指定错误地址或恶意夺取控制权的情况下存在风险。单步权限转移过程缺乏对新管理员的验证或确认步骤,可能导致意外失去对交易所管理的控制。


为了降低这些风险,建议采用两步所有权转让机制来管理交易所的管理权。该机制包括当前管理员提出转让提议和新管理员确认接受两个步骤。通过此过程,可以确保新管理员愿意并且能够接受管理角色,从而提供额外的安全性和错误预防层。


实施步骤如下:

1) 所有权转让提议:当前管理员调用一个方法来提议新的管理员。此操作将在合同状态中记录提议的新管理员地址,但不会立即转移权利。

2) 所有权接受:提议的新管理员必须通过单独的合同调用明确接受该角色。只有在执行此操作后,管理权限才会转移。


5.没有区分限价单和市价单


在 Bluefin 当前交易平台中,存在限价单和市价单两种常见的订单形式。在 evaluator 合约中,如下图 214-233 行的代码片段所示,分别通过 set_max_qty_limit 函数设置限价单的最大交易数量,而 set_max_qty_market 函数设置市价单的最大交易数量。


然而,在对交易数量进行检查时,没有区分限价单和市价单。下图代码片段所示,verify_min_max_qty_checks 函数用于验证交易数量必须大于最小的交易数量且小于最大的交易数量。但是该函数在执行交易订单时却同时执行了限价单最大交易数量和市价单最大交易数量的检查。这导致如果交易订单是限价单,也会执行市价单的最大数量检查,这是不合理的。


为了改进这一情况,建议在 verify_min_max_qty_checks 函数中区分限价单和市价单,并分别执行相应的最大交易数量检查。这样可以确保对不同类型的订单进行正确的数量检查,提高交易平台的安全性和有效性。


04

总结


通过深入在 Bluefin 审计竞赛中分析发现的问题,我们提出了针对每个问题的解决方案和改进建议,以确保 Bluefin 平台能够提供安全、高效的交易体验。在这里每个问题都得到了详细剖析,并提出了相应的应对策略。通过这些改进,我们相信 Bluefin 将能够进一步巩固其在去中心化永续交易领域的地位,为用户提供更加安全可靠的交易环境

 

MoveBit,BitsLab 旗下品牌,作为服务于 Move 生态的领先安全团队,分布于硅谷、新加坡、香港、台湾等地。作为 Aptos、Sui 等主流生态官方认可的安全审计伙伴,我们已为全球 100+ 个机构和项目提供了区块链安全解决方案,覆盖 Move 生态达 80% 以上,累计审计代码 150,000+ 行,累计保护用户资产超过 20亿+ 美元;同时深度参与到 Move 生态的核心建设,主办了 Move DevConf、MoveCTF 等一系列重要活动。若您有任何安全审计需要,欢迎随时与我们取得联系,我们将为您定制细致、全面、专业的安全解决方案,一起 Build on Move!


参考资料 Reference

[1]

https://bluefin.io/

[2]

https://hackenproof.com/

[3]

https://pyth.network/

[4]

https://github.com/pyth-network/pyth-crosschain/blob/e7bf47a18e2d9a9d983214342540691c1bada52e/target_chains/sui/contracts/sources/pyth.move#L379-L384



About

MoveBit



MoveBit,BitsLab 旗下品牌,是服务于 Move 生态的安全团队,已经陆续与全球多家知名项目合作,为合作伙伴提供安全审计服务,其愿景是让 Move 生态成为最安全的 Web3 生态


团队由学术界安全大牛和企业界安全领军人物组成,具有 10 年以上的安全经验,在 NDSS、CCS 等顶级国际安全学术会议上发表过安全研究成果,并且是 Move 生态最早期的贡献者,与 Move 开发者共同制定 Move 应用的安全标准和形式化验证的最佳实践。


- Website: https://www.movebit.xyz/

- Twitter: https://twitter.com/MoveBit_



END


点击卡片,关注 MoveBit ~

MoveBit
Move 生态安全专家