以上是_fee0Earned 和_fee1Earned 函数的实现,两个函数实现相同,都实现了这样一个公式(以_fee0Earned 为例): user.token0Rewards += PLP.balanceOf(account) * (fee0PerShare - user.token0PerSharePaid) / 1e18 也就是说,该函数会在原有的 user.token0Rewards 基础上,根据用户拥有的 PLP Token 数量计算应给用户发放的 Fee 的份额。 但我们注意到这个函数是增量的,也就是说即使用户并没有持有 PLP Token (PLP.balanceOf(account) 为 0),该函数仍会返回保存在 user.token0Rewards 中记账的存款奖励。 因此对于整个合约,我们发现两个重要的逻辑缺陷: - 用户的存款奖励是记录在 user.token0Rewards 和 user.token1Rewards 中的,并不与任何 PLP Token 或其他东西有任何形式的绑定。
- 用于取回存款收益的 collectFees 函数仅仅依赖于记账的 user.token0Rewards 和 user.token1Rewards 状态,即使用户并未持有 PLP Token,仍可以取出对应的存款奖励。
我们假想一个攻击流程: - 攻击者向机枪池中存入一定的流动性,获得一部分 PLP Token。
- 攻击者调用 collectFees(0, 0),后者会更新攻击者的存款奖励,即状态变量 user.token0Rewards 的值,但并没有真正取回存款奖励。
- 攻击者将 PLP Token 转给自己控制的其他合约,再调用 collectFees(0, 0) 更新状态变量 user.token0Rewards。也就是说通过不断地流转 PLP Token 并调用 collectFees(0, 0),攻击者复制了这些 PLP Token 对应的存款奖励。
- 最后,攻击者从以上各个地址调用 collectFees 函数,取回真正的奖励。此时虽然这些账户中并没有 PLP Token,但由于记账在 user.token0Rewards 没有更新,攻击者因此得以取出多份奖励。
用现实生活中的例子来描述这个攻击,相当于我向银行存钱,银行给了我一张存款凭证,但这张凭证没有防伪措施也没有和我绑定,我把凭证复印了几份发给不同的人,他们每个人都凭借这个凭证向银行取回了利息。 攻击流程分析通过以上的代码分析,我们发现了 Popsicle Finance 在机枪池实现上的漏洞。下面我们对攻击交易进行深入分析,看攻击者是怎样利用这个漏洞的。 攻击者的总体流程如下: - 攻击者创建了三个交易合约。其中一个用于发起攻击交易,另外两个用于接收 PLP Token 并调用 Popsicle Finance 机枪池的 collectFees 函数取回存款奖励。
- 通过闪电贷从 AAVE 借出大量流动性。攻击者选择了 Popsicle Finance 项目下的多个机枪池,向 AAVE 借出了对应这些机枪池的六种流动性。
- 进行 Deposit-Withdraw-CollectFees
(责任编辑:admin)
|