mt logoMyToken
总市值:
0%
恐慌指数:
0%
币种:--
交易所 --
ETH Gas:--
EN
USD
APP
Ap Store QR Code

Scan Download

合约遭受攻击,损失数千万美金,Avalanche 链上借贷项目 Vee Finance被黑分析

收藏
分享

撰文:BlockSec

原标题:《似曾相识燕归来:Vee Finance 安全事件分析》

北京时间 2021 年 9 月 21 日,Avalanche 链上的借贷项目 Vee Finance 的合约遭受攻击,造成数千万美金的损失(8804.7ETH 和 213.93 BTC)。

通过对相关合约和攻击交易的分析,我们发现这是一起由于该项目提供的「杠杆借贷」功能不完善导致的价格操纵攻击(即 price manipulation attack,我们的 论文 对此做了系统的阐述)。以下我们将对合约漏洞和攻击流程进行完整的分析。

 

相关背景简介

 

Vee Finance 是基于 Compound 的一个借贷项目,在 Compound 的基础上做了一些优化和功能更新。而此次被攻击的函数正是在这些新功能的一部分。

首先需要知道的是,Compound 是一个借贷平台,通过超额抵押进行借贷。Vee 在此基础上提供了一个和攻击有关的新功能:杠杆借贷。

超额借贷的意义在于,抵押价值 100 美元的 Ether 可以借出 80 美元的 USDC,当借贷者无法偿还的时候可以没收抵押物以偿还债务,而只要市场波动在一定范围内,抵押品的总价值一定大于贷出的价值,这样就避免了现实世界中借贷需要依赖信誉这个问题。

但是这样显然不够「刺激」。Vee 提供的杠杆借贷原理很简单:使用者可以指定杠杆倍数,假如杠杆为 3,那么抵押价值 100 美元的 Ether 可以借出 80*3=240 美元的 USDC。

当然,Vee 不会让用户直接拿走这笔钱(这样贷出价值就大于抵押品价值,用户可以直接违约),而是允许用户做一笔交易。

这个逻辑在合约的 borrowAndCall 函数中实现:

函数实现首先调用 borrowLeverageInternal 函数进行杠杆借贷,杠杆比率由参数 leverage 指定。然后调用 callOrderProxyInternal 执行用户指定的交易(Order)。

下面首先来看 borrowLeverageInternal (内部会调用逻辑实现 borrowFresh)的实现。大部分代码与 Compound 相同,只有处理 leverage 的时候存在不同:

当用户指定的杠杆为 0 时,执行正常的借贷逻辑,把资金直接转给借贷人;当存在杠杆时,将杠杆记录在状态变量中,然后调用 doDeposit 函数将资金转移到某个固定的位置(防止用户直接取走)。

需要注意的是,borrowLeverageInternal 的实现除了上述对 leverage 的处理之外,和原版 Compound 并没有任何不同。也就是说,对于这一笔杠杆借贷,系统只会记录单倍杠杆的借贷额。也就是说,虽然用户用 100 美元抵押借出了 240 美元,但系统记录的用户借款仍然只有 80 美元。

doDeposit 函数的实现很简单,将资金转入 orderProxy 合约中。

callOrderProxyInternal 函数的实现也很简单,就是将用户指定的 order 发给 orderProxy 进行执行。

总结一下:用户可以进行抵押借贷。当使用抵押借贷时,资金不会转给用户,而是转给 Vee 控制的 orderProxy 合约,后者会执行一个用户指定的订单。

 

漏洞成因分析

 

我们回顾一下一个正常的杠杆交易订单是怎样操作的:

  1. 假设我们拥有 1 个 Ether,想用杠杆来增加获利,Vee 正好提供了杠杆交易的功能。
  2. 我们抵押了 1 个 Ether,根据超额抵押原则,能借出 0.5 个 Ether;再加上三倍杠杆,总共可以借出 1.5 个 Ether。
  3. 但此时项目方不能把这些 Ether 转给我们,因为我们不能保证能够还得上。因此 Vee 将这些借款锁在一个代理(Proxy)合约中,由代理合约代为交易。
  4. 假设我们看好 LINK 代币,想用杠杆放大获利。假设此时市场价 1 LINK = 0.01 Ether。
  5. 为了(从我们对市场的观点)中获益,我们现在将 1.5 个 Ether 换为 150 个 LINK (第一笔交换),并让 Vee 帮我创建一个限价单,当 1 LINK = 0.02 Ether 时将所有的 LINK 换回 Ether (第二笔交换)。这些功能正是 Vee 相对 Compound 的创新。
  6. 假设我们是正确的,一段时间后 LINK 的价格上涨。此时我们在 Vee 中的限价单执行,150 个 LINK 换到 3 个 Ether。订单完成,我们获利 1.5Ether。
  7. 假设我们是错误的,一段时间后 LINK 的价格下跌,Vee 会持续监控流动性情况,当可能产生资不抵债时,Vee 会主动将限价单取消并将所有的 LINK 卖出以清算我们的所有账户。

这个逻辑的正常执行基于以下条件:Vee 在杠杆交易开始时,必须立刻检查第一笔交换的资金是否具有相同价值(在去除正常的滑点等因素之后),并持续监控用户的流动性,在流动性不足时及时清算。在上述第 4 步中,1.5 个 Ether 和 150 个 LINK 的价值是基本等价的。否则,假设 150 个 LINK 的价值只有 0.5Ether,相当于 Vee 用 1.5 个 Ether 换到了价值只有 0.5 个 Ether 的代币,会导致项目方严重亏损。

然而,我们在仔细分析了 Vee 项目的代码之后,发现整个发起杠杆交易的 borrowAndCall 调用并没有对第一笔交换的价值进行判断。同样以上面的例子为例,在调用 borrowAndCall 时,由于交易对不平衡等因素,1.5 个 Ether 只换回了 50 个 LINK (价值只有 0.5 个 Ether)。此时 Vee 项目方应该检查这笔交换的价值,判定该调用失败:因为兑换的两种代币价值不对等,此时用户已经处于亏空状态,项目方已经受损。遗憾的是,由于缺乏检查,此次调用成功执行,此时项目方已经处于无法挽回的亏损状态。

按道理来说,只要交易量足够大,不平衡的交易对会由于套利等因素逐渐平衡。对于正常的 Pangolin 交易池来说也是如此,小额交易只会产生少量滑点,因此代理合约使用用户的借出额进行交易,确实是将资金池内的某种代币换成了价值相等的另一种代币。

然而我们发现,作为 Vee 官方支持的 LINK 代币,在其依赖的 Pangolin 交易池中竟然没有交易对! Pangolin 项目的代码和 Uniswap 基本一致,相同的代币对只能创建一个资金池,而攻击者利用的 ETH-LINK 资金池,正是由攻击者自行创建的。当然,即使攻击者没有创建恶意的不平衡交易对,也可以用 Flash Loan 的方式使交易对不平衡。

综上,可以推断出下面的攻击流程:

  1. 创造一个不平衡的交易对并在 Pangolin 上注册。举例来说,创造一个用 1 个 LINK 就可以换出 100 个 Ether 的交易对 P。
  2. 调用 borrowAndCall,加上杠杆,借出大量的 Ether。指定的订单内容是在交易对 P 上将 Ether 换成 LINK。
  3. 订单执行,交易对 P 中多了很多借出来的 Ether,还给 Vee 的代理合约的只是非常少量的 LINK。至此,攻击者成功地「套」出了 Vee 的 Ether。
  4. 在交易对 P 上进行交易,用少量 USDT 换出前一步中套出的 Ether,获得大量获利。

总结来说,对于杠杆交易的实现,项目方应当在杠杆交易的开始就检查第一笔交换的前后价值,如果严重不对等,此时用户的流动性已经出现亏空,则应该直接使调用失败,避免进一步的损失。

 

攻击交易分析

 

以下,我们将以攻击交易
0x4fb222908bd87cda0336776a6d78d35ef77b0a4bad866c4530b9f0d2616af005
为例介绍攻击的主要流程。如图所示(图中省略了一些影响不大的合约调用)。

  • 图中步骤 1,攻击合约一先为攻击合约二往 VeeFinance 的 cToken 地址(合约名称为 CErc20Immutable)存入了约 0.96WETH,从而使得合约二可以进行抵押借贷(可以借出约 0.52WETH)及杠杆交易。
  • 图中步骤 2,合约一通过创建合约二的方式绕过了 isContract() 对 msg.sender 的检查,并在 constructor() 函数中进行攻击调用。
  • 图中步骤 3~5,合约二调用 CErc20Immutable 的 borrowAndCall() 函数,正如前面代码分析的,该函数在设置 leverage 倍数之后将进行杠杆交易,通过调用 VeeProxyController 去 Pangolin (类似于 UniswapV2)的池子中进行交易。注意,该池子是攻击者在攻击之前创建的交易对,所以池子的滑点受到攻击者的控制,导致 VeeFinance 的合约用加了杠杆后的约 1.55 个 WETH 只换回了约 0.27 个 LINK,造成了大量亏损。
  • 图中步骤 6~8,合约二还上借贷,取出图中 1 的抵押物并 transfer 给合约一。前面代码分析中指出,虽然攻击者通过抵押杠杆进行了远超抵押额的亏本交易,但是系统记录的用户借款却仍然只有加了杠杆之前的借贷量。因此合约二仅需还上约 0.52 的 WETH,就能取出图中 1 存入的 0.96WETH。
  • 图中步骤 9,合约一以少量(约 0.27 个) LINK 换出了约 1.55 个 WETH,从而获得了步骤 6~8 中 Vee 合约的亏损,从而攻击获利。

在我们分析的这笔交易中,攻击者这样的操作共进行了 5 次。而通过反复利用漏洞连续攻击,攻击者的最终获利颇为可观。

 

总结

 

针对 DeFi 项目的攻击已出现多次,不断地给项目方和投资者带来严重的损失。值得注意的是,本次攻击的原理与 2020 年初发生的 bZx 安全事件第一次攻击的原理非常相似。而相似的攻击不断重复表明,过往的经验和教训尚未得到应有的重视,DeFi 安全还有很长的路要走。

免责声明:本文版权归原作者所有,不代表MyToken(www.mytokencap.com)观点和立场;如有关于内容、版权等问题,请与我们联系。