发现Compound的一个严重漏洞

jackson 发布于 2022-02-22 阅读 8438

今天看了一下Venus项目的逻辑和代码,发现其存在一个较严重的漏洞,这个漏洞可能导致资金池被盗。 Venus的代码是fork自Compound的,经分析,此漏洞在Compound和Cream中也存在。

发现Compound项目里面的一个严重漏洞!

发现

今天看了一下Venus项目的逻辑和代码,发现其存在一个较严重的漏洞,这个漏洞可能导致资金池被盗。 Venus的代码是fork自Compound的,经分析,此漏洞在Compound和Cream中也存在。

利用过程

  1. 攻击者可以在资金池没有资金(刚创建,或者被取空时),注入一个最小单位的份额。
  2. 攻击者向资金池注入一定量的资金,导致资金池的净值极大。
  3. 由于资金池净值极大,普通的做市资金算得得份额都为0,因此后面做市,只有资金进入,但是份额为0,攻击者一致占有全部份额。
  4. 攻击者赎回份额,盗走所有资金。

攻击原理

_20220216163408.png

  1. 在exchangeRateStoredInternal()函数中,通过当前资金池资产(包括余额,借出资产,并扣除已归还资产)除以总份额来计算兑换率,这个兑换率可以简单理解为资金池的净值。
  2. 在资金池刚创建,或者被取空时,注入一个最小单位的份额,然后直接向资金池转入一定量的资金,根据上述净值算法,这会导致资金池的净值极大。
  3. 后面存入的资金,都是通过转入资金数量除以净值得到的,由于上一步的操作,会导致通常数量的资金所得的份额整除后为0,也就是后面做市只会转入资金,但是得到的份额数量为0!资金池只有攻击者一个人的份额。
  4. 攻击者赎回份额,由于资金池只有攻击者一个人的份额,因此所有转入的资金都会被攻击者取走。 https://github.com/compound-finance/compound-protocol/blob/23eac9425accafb82551777c93896ee7678a85a3/contracts/CToken.sol#L339

以下是在rinkeby上模拟攻击的相关交易

  1. 攻击者先正常存入资金,获得份额 https://rinkeby.etherscan.io/tx/0x5d09b73dec9268d33e3cb4d2e4451351080c09195e9b1170577d64d00ad6099b
  2. 攻击者赎回份额,仅留下一个最小单位的份额 https://rinkeby.etherscan.io/tx/0x986f13ca84ec3b25f56debf4d9e4e972c26fe36cad2ed91029146881c9a79b58
  3. 攻击者直接向资金池转入大量资金,等待其他人做市 https://rinkeby.etherscan.io/tx/0xd72b1f8b1cd755929569e4695569753a71f67ba94fbbdfe06f03fe7fe8325706
  4. 有人做市,但是获得的份额为0 https://rinkeby.etherscan.io/tx/0xbd1374915ea13f4634213dde557876c266bd800a8f96a4bbc0072333876efb74
  5. 攻击者赎回份额,盗走所有资金 https://rinkeby.etherscan.io/tx/0xf0c6b689cc89614fb27b21c483763a804b8efa3c19c4ecca89f20075a6e8a28a

漏洞特性

根据上面的过程可知,此漏洞可以被利用的条件较苛刻,通常难以被利用。但是,有两种方法可以提高攻击的可能性。

方法一

  1. 攻击者监听comptroller的MarketListed事件。
  2. 当通过MarketListed发现新的资金池创建时,立即向资金池注入资金,实行攻击。

方法二

  1. 攻击者选一个较小的资金池,注入一定的份额。
  2. 攻击者向资金池注入大量资金,导致净值变大。
  3. 之前有份额的人发现净值变大,并且单位资金的利率在降低,于是,纷纷赎回份额,资金池份额降低。
  4. 资金池份额降低,攻击者循环执行2,3步骤,继续向资金池注入资金,导资金池净值快速上升,最终达到超大净值,后面进入的普通资金无法得到份额。
  5. 攻击者按照空资金池的攻击方式,盗走后来者的资金。

总结

如果这样考虑,则这个漏洞被利用的可能性就提高了很多。这种利用小份额进行的攻击,在uniswap中已经给出了一个解决方案,就是在第一次做市时往0地址转入一个小份额锁死,从而可以有效阻止这种攻击手段。

相关文章

7 条评论

该攻击并不存在,原因如下:

  1. 用户可以领取的资产等于 攻击者铸币份额*净值。 文章的攻击方式在攻击者铸币份额不变的情况下,攻击净值。 2.净值 = 借贷池现金/借贷池的总铸币数量 ,即 现金/铸币量 。 文章攻击方式是,直接通过Token转账来使得借贷池现金量变大。 3.当攻击者直接转帐后,下一个存款用户在存款时使用的 净值已经包含了该攻击者转入的资产,会导致攻击者转入的资产,被记录到下一个存款用户明下。
2022-02-23 15:28
jackson 回复 七哥

主要是有这样一种情况,就是当通过前面的操作,让净值达到了一个极大的值,后面存入的正常数量的资产,除以净值后,得到的份额是0了!

2022-02-23 15:46

当借贷池资金份额很小的时候,通过直接转入Token让借贷池净值变大,导致后续用户存入资产但不能获得份额。所以如果攻击之后,其他人存入的资产规模不足够大的话,得到的份额都是0,攻击者的份额就不会变。文章说的情况就成立了。

2022-02-23 16:46

是的,就是这个道理

2022-02-23 17:19

您好,您的第一篇文章讲述的攻击还是不太现实,因为totalsupply是在放入池子中的时候直接获得的,totalcash和totalcash之间没有明显变化,所以只是兑换的比例比较小,不存在cTokens的totalsupply仍然很小导致后面用户兑换比例特别高而吃亏

2022-03-23 18:10

先做出一个最小单位的分额,然后关键的一步,就是直接向资金池转账,这样资金池的净值就会极大了

2022-03-24 16:16
Tee

整个攻击过程本质的原因是:份额精度截断。也就是说拉高 净值也就是兑换率 使得用户 存款/兑换率 < 份额最小精度 ,从而达到用户存款却得不到任何份额的目的

2023-04-23 11:29