NFTCloudStaking逻辑漏洞分析

说明

基本信息

漏洞函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function deposit(uint256[] calldata _tokenIds, address _referrer)
public
nonReentrant
{
require(validTokenIds(_tokenIds, msg.sender), "nfts is invalid");
uint256 _amount = 0;
bool isVipNFT = false;
.....
}


function validTokenIds(uint256[] calldata _tokenIds, address _owner)
internal
view
returns (bool)
{
if (_tokenIds.length == 0) return false;

ICloudNFT.NftInfoReturns memory _nftInfo = cloudNft.getNftInfo(
_tokenIds[0]
);
bool isVIP = _nftInfo.bunnyId >= 3;

for (uint256 i = 1; i < _tokenIds.length; i++) {
ICloudNFT.NftInfoReturns memory nftInfo = cloudNft.getNftInfo(
_tokenIds[i]
);
bool _isVIP = nftInfo.bunnyId >= 3;
if (_isVIP != isVIP) {
return false;
}
if (
cloudNft.ownerOf(_tokenIds[i]) != _owner ||
nftInfo.lockUntil > block.timestamp
) {
return false;
}
}
return true;
}

作为关键的函数是在与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
2
ICloudNFT.NftInfoReturns memory nftInfo = cloudNft.getNftInfo(_tokenIds[i]);
_amount = _amount.add(nftInfo.basePrice);

计算_amount的结果是:5000000000000000000000

payDirectCommission

故名思义,就是用来为推荐人发放奖励。通过对代码过程的分析,referrer的地址就是攻击者的地址。

0x16b904b61a5104e270521e759d27520bb97f1dd0

1
2
3
4
5
6
7
8
9
10
// pay direct commission
if (address(nftCloudReferral) != address(0)) {
address referrer = nftCloudReferral.getReferrer(msg.sender);
if (referrer != address(0)) {
nftCloudReferral.addTotalFund(referrer, _amount, 0);
if (lockingPeriod > 0) {
payDirectCommission(_amount, referrer);
}
}
}

此时payDirectCommission(_amount, referrer)对应的参数内容是:

深入到payDirectCommission内部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function payDirectCommission(uint256 _amount, address referrer) internal {
address[] memory team = nftCloudReferral.getTeam(referrer);
uint256 validRef = 0;
uint256 directCommissionAmount = _amount.mul(directCommission[0]).div(
1e4
);
for (uint8 i = 0; i < team.length; i++) {
if (totalStake[team[i]] >= 1000 * 1e18) {
validRef++;
}
}
if (validRef >= 5) {
directCommissionAmount = _amount.mul(directCommission[1]).div(1e4);
}
if (validRef >= 10) {
directCommissionAmount = _amount.mul(directCommission[2]).div(1e4);
}
safeRewardTokenTransfer(address(referrer), directCommissionAmount);
nftCloudReferral.recordReferralCommission(
referrer,
directCommissionAmount
);
}

directCommissionAmount,对应的值是 2000000000000000000000000;

validRef,最终的值是1。

那么在payDirectCommission最终就会调用safeRewardTokenTransfer(address(referrer), directCommissionAmount);

safeRewardTokenTransfer

1
2
3
4
5
6
7
8
9
10
11
function safeRewardTokenTransfer(address _to, uint256 _amount) internal {
uint256 cloudAmountToTransfer = _amount.mul(1e8).div(
rewardTokenPrice()
);
uint256 rewardTokenBal = rewardToken.balanceOf(address(this));
if (cloudAmountToTransfer > rewardTokenBal) {
rewardToken.transfer(_to, rewardTokenBal);
} else {
rewardToken.transfer(_to, cloudAmountToTransfer);
}
}

根据上面的计算,最终safeRewardTokenTransfer()的参数如下:

  • _to: 0x16b904b61a5104e270521e759d27520bb97f1dd0
  • _amount2000000000000000000000000

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。

发生攻击之后,也立马变成零。

参考

https://twitter.com/peckshield/status/1629065232827187200

https://twitter.com/BlockSecTeam/status/1629097425771319296

文章作者: Oooverflow
文章链接: https://www.oooverflow.com/2023/03/03/NFTCloudStaking/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Oooverflow