'CryptoYC'

Cover事件分析

2020年12月28日上午08:08:12 + UTC ,Cover Protocol的挖矿合约(Blacksmith)出现漏洞滥用现象。

根本原因

正如许多人解释说,这行代码导致在deposit时update池状态对矿工的状态无效。

自从经过审核之日起部署该合约以来,Cover Protocol Blacksmith合约中就存在该漏洞。

一名社区成员在UTC于12月27日下午6点通知Cover团队,他们的通过deposit获得了一些cover奖励。在调查了该问题之后,Cover团队尝试通过实施以下措施来缓解该问题:

  1. 在deposit操作之前添加update pool操作,以便在用户存款时更新池,这将缓解使用UI的用户的问题。
  2. 同时启动一项定时任务,每20分钟运行一次,以更新最近未更新的任何池。

池更新的频率越高,矿工获得的额外奖励就越少。Cover团队分析认为,以上两项应将问题缓解该漏洞,让漏洞值得被黑客利用。实际上,团队没有预料到黑客攻击导致额外奖励被放大很多倍,造成了巨大损失。

错误的主要来源是矿工存款的数量与池预存款中存在的总lp Tokens之间的差。差异越大(例如1wei与1e18 wei),上述错误引起的额外奖励就越大。如果差异足够大,那么经过的时间就无关紧要,矿工仍然可以获得无限数量的COVER代币。

如果Grap Finance与合约发生了互动,则池中会剩下1 wei。它将奖励乘以1e18 +,从而铸造了40万兆个COVER。

这种情况发生在所有存入少于1e18 lpToken的所有池中,其中大多数是新池,并且Cover Protocol: Exploiter 1(0xf05ca010d0bd620cc7c8e96e00855dde2c2943df)是第一个采取行动的人。

Exploiter 1的时间表

2020年12月28日至04:09:27 + UTC

通过团队的多重签名交易向Blacksmith合约添加了一个新的Balancer池。

2020年12月28日至08:08:12 + UTC

攻击者执行了合约的第一笔存款,存入1,326,880个BPT令牌

image

source:https://etherscan.io/tx/0xd721b0ef2886f14b75548b70d2d1fd82bea085ca24f5de29b833a64cfd8f7a50

2020年12月28日至08:11:16 + UTC

然后,同一攻击者调用withdraw函数,提取了个703.64 COVER以及 1,326,878.99 个BPT

source:https://etherscan.io/tx/0xadf27f5dd052482d46fdf69a5208a27cc7352522c7c19bbde5aee18f6ea4373b

2020年12月28日至08:47:15 + UTC

可在此处找到被利用的COVER代币的首次出售:

image

source:https://etherscan.io/tx/0x66128a1685605b1798c852e14db0b0232a56e3bebf7f3f35b168642801754beb

在此期间,有多个帐户滥用该漏洞,并在市场上出售其COVER。

2020年12月28日至09:18:28 + UTC

攻击者继续铸造,并且攻击媒介仍然存在。

image

source:https://etherscan.io/tx/0xf81fb72ee096e0d7afe4b99a55b723110604fb26ec82846043cfc396e1fa79da

总共,Exploiter 1窃取了大约440万美元的用户资金,并将其转移到了该地址。Cover团队正在积极跟踪此地址以及其他参与该漏洞利用的地址。

第二波大的攻击:grap.finance的时间表

2020年12月28日至11:54:47 + UTC

grap.finance部署者将15,255.552810089260015362 BPT(DAI /基础池)存入了blacksmith合约。

image

source:https://etherscan.io/tx/0x77490baee41a9b35a6e87d49453c7329c7517c10ce6ce26b4c142692a2877e65

2020年12月28日至11:58:04 + UTC

grap.finance:部署者撤回了15,255.552810089260015361 BPT(DAI /基础池),在blacksmith合约中仅剩1 wei。

image

source:https://etherscan.io/tx/0x88ce99fc1cb695db82d83ce5fe587396744841d3a123687f95b18df6a3106818

2020年12月28日至11:58:56 + UTC

另一个用户从合约那里提取了他的大部分余额(1,007.599009946121991627 BPT)。现在,仅grap.finance就拥有合约上DAI /基础池的全部流动资金,正好为1 wei。

image

source:https://etherscan.io/tx/0xa27fb73caddb1cf24aa7a5afe84eed13db2f0a889a6ee0f3d5e6226a76c0fd9c

2020年12月28日至12:00:21 + UTC

Grap Finance:部署者根据blacksmith合约退回15,255.552810089260015361 BPT(DAI /基础池)。

image

source:https://etherscan.io/tx/0xbd1fcda7006ddd58b18cb3bfbd01ef2d1a979be596e1c73be1d7d65fd7eb8215

2020年12月28日12:02:04 + UTC

Grap Finance:部署者要求奖励,并且由于只有1 wei的余额加上存储/内存问题,导致铸造了40,796,131,214,802,500,000.212114436030863813 $ COVER。

image

source:https://etherscan.io/tx/0xca135d1c4268d6354a019b66946d4fbe4de6f7ddf0ff56389a5cc2ba695b035f

2020年12月28日至12:29:03 + UTC

Grap Finance:Deployer开始通过多次交易中的1inch.exchange出售尽可能多的代币。

image

source:https://etherscan.io/tx/0xaf94d9b537a13819e873b37160594af2b1cc70b420d0b160a02e341566866a6b

source:https://etherscan.io/tx/0x01b3517845ed9c6b7b40d57bd71ac1a89fec080c5b8988f764d8226ac5caa959

2020年12月28日至12:59:27 + UTC

grap.finance burn 掉了铸造的所有令牌

image

source:https://etherscan.io/tx/0xe6c068ca3605228b2435a414f2b372057340f77d3fe9f1d3967eb1ad128cb5d2

2020年12月28日下午01:41:01 + UTC

Grap Finance:部署者通过出售$ COVER将他们提取的4351(1 + 4350)ETH发送到cover部署者帐户,该帐户占漏洞利用总损失的34%(940万美元)

image

source:https://etherscan.io/tx/0x23cb9bdf14eed955a84da3f3cfcf296356c0f897dec0b99e85151a7f084a3051

image

source:https://etherscan.io/tx/0xc2fd5094c1e108f83222a86bd46b35fc0da35616385d681964b22003643f982e

对于漏洞的产生原因分析

Solidity中的Storage和Memory关键字类似于计算机的硬盘驱动器和计算机的RAM。与RAM相似,“固态存储器”是存储数据的临时位置,而“存储”在函数调用之间保存数据。Solidity Smart Contract在执行期间可以使用任何数量的内存,但是一旦执行停止,该内存将被完全擦除以进行下一次执行。另一方面,存储是持久性的,而智能合约的每次执行都可以访问先前存储在存储区域中的数据。

以太坊上的每笔交易都会花费我们一定量的gas。gas消耗越少,Solidity代码越好。与存储的gas消耗相比,内存的gas消耗不是很明显。因此,始终最好使用Memory进行中间计算并将最终结果存储在Storage中。

  1. 默认情况下,结构,数组的状态变量和局部变量始终存储在存储器中。
  2. 函数参数在内存中。
  3. 每当使用关键字“memory”创建数组的新实例时,都会创建该变量的新副本。更改新实例的数组值不会影响原始数组。

下图是创建了一个合约来演示’storage’关键字的示例:

image
image

当我们在上面的代码中检索数组编号的值时,请注意,数组的输出为[0,2]而不是 [1,2]。

第二个关于关键字memory的示例:

当我们在上面的代码中检索数组编号的值时,请注意,数组的输出为[1,2]。在这种情况下,更改myArray的值不会影响数组编号中的值。

因此,对于blacksmith合约的分析如下:

当前协议利用 pool.accRewardsPerToken 来计算第 130 行的 miner.amount.mul(pool.accRewardsPerToken).p(CAL_MULTIPLIER) ,由于 118 行的 pool 类型为 memory, 而 121 行的函数 updatePool()并未对其进行更新,导致最终计算出来的 rewardWriteoff 比预期的数额小。

在 DeFi 的世界里,崇尚「代码即法律」(Code is Law),但是defi安全问题层出不穷,这更应该引起我们的警惕。