Popsicle Finance漏洞分析
攻击信息:
攻击者地址:
0xf9E3D08196F76f5078882d98941b71C0884BEa52
攻击合约:
A:
0xdfb6fab7f4bc9512d5620e679e90d1c91c4eade6
B:
0x576Cf5f8BA98E1643A2c93103881D8356C3550cF
C:
0xd282f740Bb0FF5d9e0A861dF024fcBd3c0bD0dc8
攻击交易:0xcd7dae143a4c0223349c16237ce4cd7696b1638d116a72755231ede872ab70fc
流程分析:
首先看到攻击者,通过闪电贷借出大量的资金。
然后通过添加流动性,获得PLP代币10.52个。
在然后把PLP代币发送给攻击合约B,攻击合约B再发送给攻击合约C,最后再把PLP发送给合约A,来取消流动性。
然后往下看,就看出问题了,为什么会向攻击合约B和攻击合约C转账了?
代码分析:
从上面知道,攻击合约得到了PLP代币,然后发送给了合约B。这里我们知道问题出在转账上面。跟踪到这一处调用。
从图中,可知调用了SorbettoFragola合约中的collectFees函数,这里直接debug,一步一步看。
第一步:
攻击合约B调用,SorbettoFragola合约中的collectFees函数,然后进入updateVault修饰器中的_updateFeesReward函数。account是攻击合约B。
进入到_updateFeesReward函数,这里我们一步一步看。
这里返回的零,没有什么异常,在往下面走。
返回的的是"90984415496720749373",没有异常。
进入_fee0Earned函数,我们看到output就有问题了,因为对应参数user.token0Rewards,为"957182808388617756829",在往下面跟。
问题出来了,balanceOf获取的是用户的PLP余额,并带入计算。恰好是传入的10.52。最后调用collectFees函数退出流动性。
1 function collectFees(uint256 amount0, uint256 amount1) external nonReentrant updateVault(msg.sender) { 2 UserInfo storage user = userInfo[msg.sender]; 3 4 require(user.token0Rewards >= amount0, "A0R"); 5 require(user.token1Rewards >= amount1, "A1R"); 6 7 uint256 balance0 = _balance0(); 8 uint256 balance1 = _balance1(); 9 10 if (balance0 >= amount0 && balance1 >= amount1) { 11 12 if (amount0 > 0) pay(token0, address(this), msg.sender, amount0); 13 if (amount1 > 0) pay(token1, address(this), msg.sender, amount1); 14 } 15 else { 16 17 uint128 liquidity = pool.liquidityForAmounts(amount0, amount1, tickLower, tickUpper); 18 (amount0, amount1) = pool.burnExactLiquidity(tickLower, tickUpper, liquidity, msg.sender); 19 } 20 user.token0Rewards = user.token0Rewards.sub(amount0); 21 user.token1Rewards = user.token1Rewards.sub(amount1); 22 emit RewardPaid(msg.sender, amount0, amount1); 23 }
攻击合约调用collectFees函数主要是利用函数中的修饰器触发_updateFeesReward
在利用获取的balanceOf,去更新奖励。就相当于一个凭证,可以多次使用。