说明
基本信息
Attacker: 0x16b904b61a5104e270521e759d27520bb97f1dd0
Attack Contract: 0x09845b8e7d94c559697bfa860cea69bbc7b0495f
Vulnerable Contract:
- 0x1954b6bd198c29c3ecf2d6f6bc70a4d41ea1cc07
- 0x9B15F60D0bb30b125904690C852aF696A3531E96
Attack Tx:https://bscscan.com/tx/0x4897b7fb241c72dc96d97ba4ad42c00654d47cb3961239f5088b878942b4d48d
LOSS: 100 CloudNFT
漏洞函数
1 | function deposit(uint256[] calldata _tokenIds, address _referrer) |
作为关键的函数是在与uint256 i = 1; i < _tokenIds.length; i++,循环从1开始,这样就导致了tokenIds[0]绕过了检查.
具体来说,用户可以存入 CloudNFT 并领取Cloud作为奖励。根据设计,一个 CloudNFT 只能存入一次以领取奖励。但是,质押合约不会检查第一个存入代币 (tokenIds [0])的质押状态.
漏洞分析
明白了validTokenIds()存在的漏洞之后,就很容易理解攻击者的操作了。
攻击者通过只存入一个CloudNFT来绕过验证,并重复这样的过程并最终获得大量的奖励。我们就对其中的一次行为进行分析。
deposit
质押一个Token,即
1 | NftCLoudStaking.deposit(_tokenIds=["146"],_referrer=0x16b904b61a5104e270521e759d27520bb97f1dd0) |
validTokenIds
根据上面的分析,当质押一个NFT时,就可以绕过验证。
计算_amount
_amount最终的结果就是攻击者获得的质押奖励。
根据代码逻辑:
1 | ICloudNFT.NftInfoReturns memory nftInfo = cloudNft.getNftInfo(_tokenIds[i]); |
计算_amount的结果是:5000000000000000000000
payDirectCommission
故名思义,就是用来为推荐人发放奖励。通过对代码过程的分析,referrer的地址就是攻击者的地址。
即0x16b904b61a5104e270521e759d27520bb97f1dd0
1 | // pay direct commission |
此时payDirectCommission(_amount, referrer)对应的参数内容是:
深入到payDirectCommission内部
1 | function payDirectCommission(uint256 _amount, address referrer) internal { |
directCommissionAmount,对应的值是 2000000000000000000000000;
validRef,最终的值是1。
那么在payDirectCommission最终就会调用safeRewardTokenTransfer(address(referrer), directCommissionAmount);
safeRewardTokenTransfer
1 | function safeRewardTokenTransfer(address _to, uint256 _amount) internal { |
根据上面的计算,最终safeRewardTokenTransfer()的参数如下:
_to:0x16b904b61a5104e270521e759d27520bb97f1dd0_amount:2000000000000000000000000
transfer
计算 cloudAmountToTransfer
rewardTokenPrice() 计算20000000。
1 | uint256 cloudAmountToTransfer = _amount.mul(1e8).div(rewardTokenPrice()); |
cloudAmountToTransfer的计算方法:
1 | 2000000000000000000000000 * 1e8 / 20000000 = 1e+25 |
计算 rewardTokenBal,代码中的计算方法是:
1 | uint256 rewardTokenBal = rewardToken.balanceOf(address(this)); |
计算得到:989097370832761087059275
最终调用rewardToken.transfer(_to, cloudAmountToTransfer);
向0x16b904b61a5104e270521e759d27520bb97f1dd0 转账 1000000000000000000000)
这就是攻击者抵押一次获得的NFT的数量,即1个NFT。
获利
攻击者通过100次的重复质押,最终获取了100个CloudNFT,按照当时的价格计算,总价值大约是:81K。
发生攻击之后,也立马变成零。