Resupply黑客事件分析
borrow
:借出资产,在这里指 reUSD
。是 resupply
协议的借出资产。colateral
:抵押物,在这里指 wstUSR
。Resupply
Vault
金库合约中的存款凭证。underlying
:底层资产,在这里指 crvUSD
,属于 Curve
协议的稳定币。整套闭环的最底层资产依赖。LTV
:loan to value
抵押率。例如70%
则为抵押 100u
最多能借出 70u
我这里简单分析下
Resupply
协议的运作方式,如果有错误,请指正。
这是一个经典的 Defi
组合场景,流动性质押 + 金库质押 + 借贷协议 三重协议来达到一鱼多吃的效果。具体表现为:
流动性质押:用户在 Curve
这个 DEX
项目中提供流动性,赚取去中心化交易所的手续费收益和额外的奖励。在提供 crvUSD
这个稳定币作为流动性的同时,Curve
会给流动性提供者 mint
出 LP
代币,即 cvcrvUSD
,作为将来如果流动性提供者想要换回原资产的凭证。
金库质押:由于 cvcrvUSD
也是一种资产(只要其他项目认可的情况下),所以 cvcrvUSD
又可以通过存款的方式放入 Resupply
的金库协议中,作为一种质押代币所存在。同时,在 Resupply
Vault
的金库协议中,也会给存款者 mint
出相应份额的 wstUSR
作为凭证,同样是作为将来取回 cvcrvUSD
而存在的。在金库合约中,金库本身会用用户存款的 cvcrvUSD
去做投资,进而达到 wstUSR
升值或者给用户分发 CVX
等的一些代币作为回报。
借贷:由于 wstUSR
也是一种资产,所以 wstUSR
又可以通过抵押进 ResupplyPair
中,去通过借贷协议借出稳定币 reUSD
。而借出的 reUSD
作为一种稳定币,又能被用户拿去其他地方做投资使用。
这里描述的是正常的 Defi
循环场景,下面我将来简单分析下这次 Resupply
被盗事件中的协议漏洞及黑客是如何盗取资金的。
在用户的借款中,每次借款都会去调用 isSolvent
函数修饰器,去检查这个用户有没有资格借款。这次的漏洞就出现在这个 isSolvent
函数修饰器中。
具体进到修饰器中看,这里主要是进行了一个判断:判断用户总借款金额是否超过上限。
例如:
总借款上限 maxLTV
假设为 85%
,则每次用户调用借款都会去判断 LTV
是否超过 85%
。图上主要依靠的计算公式为:((借款总额 * 汇率)) / 抵押物金额
。这个公式为图上的简化。
由此可知,当用户每次借款时,借款总额都会累加,直到 LTV
超过 maxLTV
。
那么,问题来了,假如在这个借款的过程中,_exchangeRate
(汇率)是可以被操控的话,让 _exchangeRate
恒等于 0
或者极接近于 0
。那么用户则会一直能借出 reUSD
且都是大额的借出。
此次黑客事件也是基于这个汇率做文章。
那么黑客是如何进行汇率的操控的呢?继续追踪下去,可以看到,在这个合约中,汇率的计算是通过 汇率 = 1e36/ 抵押品价格
如果要让这个 _exchangeRate
等于 0 (除法下溢可以做到)或者非常接近于 0
,则这个抵押品(wstUSD
)的价格要足够大(price
代表了一个 wtUSR
可以借出多少 reUSD
)。
则此时黑客需要操控的 price
(分母)变得很大很大。
而在合约协议中,这个 price
是通过预言机合约调用 getPrice
函数去获取的。那么我们继续追踪下去。
在预言机合约中,实际上并没有做什么事情,仅仅是做了一次金库合约的调用转发。
那么此时问题就来了,预言机对于价格的转发高度依赖 Vault
金库合约中的 _convertToAsset
函数,而不是真实市场上的价格(链下预言、喂价)。假如黑客有能力操控 Vault
金库的价格,那他就能操控借贷合约去实现 _exchangeRate
等于 0
或者接近于 0
的目标,进而攻击这个漏洞。
实际上,本次黑客攻击也的确是这样干的。具体是如何实现的,可以看下面金库合约
继续追踪到金库 Vault
合约中,我们可以看到在 _convertToAsset
函数中,实际是做了一次资产的换算,也就是计算在金库中的质押份额乘上总的金库资产。
那么这里就有一个问题,假如这个金库资产是空的金库(total_asset = 0, totalSupply = 0
),或者资产的深度很小。那么黑客可以通过绕过 deposit
函数,而是直接打钱(捐赠)的方式让 total_asset
总资产变得很大,在 shares
不变(仍为 0
,但黑客可以 deposit
极少量的 cvcrvUSD
进去,使得 shares
不为 0
)的情况下,使得这个函数返回的值变得很大。进而达到攻击的目的。
就假如说,这个金库是一个接收 cvcrvUSD
,返回 wstUSR
的金库。那么根据这里的计算,黑客给这个空金库捐赠大额 cvcrvUSD
。然后黑客 deposit
存入很少的 wstUSR
即可算出少量 wstUSR
可以换出大量 cvcrvUSD
。
又因为这个金库实际上是个空的金库,只有黑客在操作,所以这个返回值变得很大了后,黑客可以可以让前面的汇率接近 0
或者等于 0
了。进而达到攻击的目的了。
Resupply
协议部署一个抵押 wstUSR
借出 reUSD
的新借贷合约。其中这个借贷合约依赖了 Resupply
Vault
合约的 wstUSR
的汇率。Vault
金库中的打入大量 cvcrvUSD
资产。然后黑客通过正常 deposit
的方式存入很少量的 cvcrvUSD
,获取极少的 wstUSR
。又因为这个金库中实际上是个空金库,所以此时 wstUSR
对 cvcrvUSD
的汇率极高。仅一点点 wstUSR
可以换取大量 cvcrvUSD
。(实际外部市场上汇率没这么高)Vault
市场的动作,使得借贷合约中 exchangeRate = 1e36 / price = 0
(被整除下溢),所以导致借贷合约中_isSolvent()
被判定为安全,TVL
始终等于 0
。isSolvent
一直返回 true
,借贷合约可以被无限借出(不超限额的情况下)。则黑客此时可以用很小的 wstUSR
换取大量的 reUSD
。直到达到上限。核心原因是 Resupply
这个借贷协议太傻逼了,高度依赖 Vault
金库合约中的局部市场价格汇率。要是有空池检测或者真实市场预言机就这种漏洞根本不会发生。这就是我觉得 ChainLink
的代币值得观察的原因,真实世界预言机对于区块链生态而言真的太重要了。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!