2022年4月30日,攻击者利用与Nerve Bridge事件相同的漏洞攻击了Saddle Finance。总共有4,900个以太坊被攻击。幸运的是,其中1,360个以太坊被我们成功挽救。有关此事件的详细描述,请参阅官方事后分析报告。


尽管利用了相同的漏洞,但攻击方式与之前的不同。由于新的攻击方式并非如人们预期的那样直观,我们认为它值得进行更深入的调查。在本报告中,我们将首先简要阐述该漏洞,然后回顾Nerve Bridge事件的原始攻击方法。之后,我们将重点分析Saddle Finance事件,剖析其攻击过程以揭示新的攻击方式。
0x1. 关于部署的合约
相关合约地址如下:
- 受害者MetaSwap合约:0x824dcd7b044d60df2e89b1bb888e66d8bcf41491
- 存在漏洞的MetaSwapUtils合约:0x88Cc4aA0dd6Cf126b00C012dDa9f6F4fd9388b17
值得注意的是,与已验证的MetaSwap合约相关联的MetaSwapUtils合约的显示代码与部署的MetaSwapUtils合约不等效,后者在Settings中指定了地址,如下所示:

所以请不要混淆这两个MetaSwapUtils合约:)
0x2. 漏洞分析
存在漏洞的合约属于MetaPool,这在之前的博客中已详细讨论过。 简而言之,MetaPool最初由Curve设计,允许单个代币与另一个(基础)池中的所有代币进行兑换,而不会稀释其流动性。它本质上是一个由稳定币和基础池的LP代币组成的池,而基础池包含几个其他稳定币。 MetaPool设计存在一个问题,即MetaPool基本上是一个维护稳定币价格的稳定币池,而基础稳定币池的LP代币不是稳定币。
事实上,基础稳定币池的LP代币价格可以通过调用基础池的getVirtualPrice函数来获得,其价格会随着基础池收取的费用累积而稳步上涨。
为了处理这种情况,MetaPool在计算价格之前会扩大LP代币的储备,如下所示。

因此,如果用户将LP代币兑换为稳定币,LP代币的数量将在计算价格之前进行放大。 或者,如果用户将稳定币兑换为LP代币,计算出的LP代币数量将在转移和记账之前进行缩小。

上面swapUnderlying函数的代码片段用于在MetaPool中的稳定币与基础池中的稳定币之间进行兑换。如两个红色矩形所示,该函数会放大进入的LP代币的数量,并缩小兑换的LP代币的数量。
然而,swap函数的实现与swapUnderlying函数的实现不一致。具体来说,漏洞的根本原因在于swap函数(即_calculateSwap函数)中实现的有缺陷的计算,该计算没有正确缩小和放大LP代币的数量。如下所示,左侧是MetaPool的漏洞代码,右侧是修复后的版本。

Nerve Bridge事件的攻击者利用了swap函数和swapUnderlying函数之间的不一致性。
(请注意,Nerve Bridge是Saddle Finance的一个分叉项目。)
之后,Saddle Finance修复了该漏洞,并立即重新部署了MetaSwapUtils库的新版本(即V2)。
不幸的是,由于未知原因,以太坊上的sUSD V2 MetaPool仍部署了旧的、存在漏洞的MetaSwapUtils库。 因此,该漏洞在4月30日再次被攻击者成功利用。有趣的是,与Nerve Bridge事件中使用的攻击方法不同,攻击者采用了一种不同的方式来攻击存在漏洞的MetaPool。
0x3. Nerve Bridge事件的原始攻击方法
我们重用下面的图(参见之前的博客)来回顾原始攻击方法。

由于存在漏洞的swap函数没有缩小兑换的LP代币(Nerve 3-LP)的数量,步骤3中兑换的数量(36,959)比正常值要大。
然后攻击者调用(没有漏洞的)swapUnderlying函数,将36,959 Nerve 3-LP(在步骤4和5中)卖出,获得51,494 fUSDT,获利1,143 fUSDT。
该利润可以合理地解释为:攻击者在步骤3中获得了更多的Nerve 3-LP,然后攻击者利用swap函数和swapUnderlying函数之间不一致性,以“正常”价格将其卖出。
0x4. Saddle Finance事件的新攻击方法
近期Saddle Finance事件的攻击者使用了一种不同的方式攻击了同一个存在漏洞的swap函数,而没有涉及swapUnderlying函数。
在此,我们以一个攻击交易为例,详细说明其过程。

直观上看,似乎不应该存在任何利润,因为步骤3和步骤4的任何隐含操作都会被抵消。
具体来说,在步骤3中,由于LP代币(即saddleUSD)兑换时没有进行缩小,攻击者可以兑换出更多的saddleUSD。
然而,在步骤4中,由于存在漏洞的swap函数在计算价格之前没有放大进入的saddleUSD的数量,攻击者不可避免地兑换出更少的sUSD。
然而,如上图所示,攻击者通过步骤3和步骤4的兑换对获利了2,059,771 sUSD。 为了弄清楚利润的原因,我们必须深入研究定价机制,并进行深入调查以理解攻击过程。
0x4.1 定价机制
Saddle Finance的MetaPool继承了Curve的定价公式:

当n等于2时,该函数的图如下图蓝色曲线所示。 (该公式的设计可在Curve StableSwap白皮书中找到。)

问题来了:MetaPool如何使用此公式计算每次兑换的价格?
假设n为2,用户使用dx0代币0兑换dx1代币1。
我们可以模拟dx1的计算过程。
在每次兑换中,A可视为常数,
而D是唯一可以影响价格曲线的变量。事实上,
D会随着池子收取费用的累积而增加。具体来说,
计算过程可总结为以下三个步骤:
- 步骤I:将当前池子的储备(x0和x1)代入公式计算当前的
D,该D决定了当前的价格曲线。 - 步骤II:将x0增加dx0,并将当前的
D和x0代入公式计算新的x1。 - 步骤III:然后,dx1是新的x1与旧的x1之间的差值。
如果代币0是基础池的LP代币,那么步骤II将变为:

这里,baseVirtualPrice/1e18在攻击期间约为1.0033。 或者,如果代币是基础池的LP代币,那么步骤III将变为:

为了理解D如何影响价格曲线,我们也用一个例子来描述。
假设用户首先用dx0代币0兑换dx1代币1,然后用dx1代币1兑换dx0'代币0。

如上图所示,由于步骤②对第一次兑换收取了费用,D增加了,将价格曲线向上移动(从黑色曲线变为蓝色曲线)。
此外,该图清楚地描述了为什么dx0'小于dx0'。
0x4.2 攻击分析
为了分析利润原因,我们部署了存在漏洞和已修复的MetaSwapUtils库,并利用攻击时受害者池的状态进行了模拟。
此外,在此模拟中,我们还记录了一些有助于理解攻击过程的数值,即此时A为10,000,x_sUSD为8,130,463,x_saddleUSD为9,688,608,D为17,818,392。

上图描述了获利兑换对的过程:
- 兑换-I:用14,800,272 sUSD兑换9,657,586 saddleUSD
- 兑换-II:用9,657,586 saddleUSD兑换16,860,043 sUSD
具体来说,兑换-I可分为以下两个步骤:
- ①:用14,800,272 sUSD兑换9,625,654 saddleUSD。此时,
D因收取的费用而增加至17,931,435。 - ②:由于存在漏洞的MetaPool没有缩小兑换的saddleUSD数量,池子损失了31,932 saddleUSD。损失使
D减少至15,736,195,这进一步将价格曲线向下移动(从黑色曲线变为灰色曲线)。
同理,兑换-II也可分为两个步骤:
- ③:由于价格曲线向下移动,用相同的9,625,654 saddleUSD兑换出16,891,906 sUSD,这远超成本:14,800,272 sUSD。
- ④:由于存在漏洞的MetaPool在计算价格之前没有放大进入的saddleUSD的数量,MetaPool中留下了31,863 saddleUSD,这使价格曲线向上移动(从灰色曲线变为蓝色曲线)。 尽管如此,兑换对仍然获利2,059,771 sUSD。
显然,上述分析清楚地解释了攻击者为何能够通过新的攻击方法获利。 此外,由于兑换-II中MetaPool中剩余的sUSD,原始攻击方法似乎比新的攻击方法更有效。当然,攻击者可以发起多次攻击来耗尽池子,这在实际中已经观察到。
0x5. 一些总结
调查表明,两次事件中获利根本原因相同。具体来说,第一次兑换(兑换LP代币)会降低存在漏洞的MetaPool的D值,从而进一步向下移动其价格曲线。这种移动极大地影响了后续的定价,是后续获利的主要原因。
关于BlockSec
BlockSec是一家开创性的区块链安全公司,由一群杰出的全球安全专家于2021年创立。公司致力于提升新兴Web3世界的安全性和可用性,以促进其大规模采用。为此,BlockSec提供智能合约和EVM链安全审计服务,用于主动进行安全开发和威胁拦截的Phalcon平台,用于资金追踪和调查的MetaSleuth平台,以及为Web3开发者在加密世界高效冲浪的MetaDock扩展。
截至目前,公司已服务超过300家知名客户,如MetaMask、Uniswap Foundation、Compound、Forta和PancakeSwap,并已获得两轮融资,融资金额达数千万美元,投资方包括Matrix Partners、Vitalbridge Capital和Fenbushi Capital等。
官方Twitter账号:https://twitter.com/BlockSecTeam



