Trick or Treat

First Flight #27
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: low
Invalid

Denial of Service (DoS) and Storage Bloat Vulnerability

Severity: High
Vulnerability Type: Denial of Service (DoS), Storage Bloat


Overview

The SpookySwap contract’s trickOrTreat function allows users to purchase NFTs at three different price points: standard price, double price ("trick"), and half price ("treat"). However, the double-price ("trick") mechanism introduces a critical vulnerability that can lead to denial of service (DoS) and excessive storage consumption, posing significant risks to the contract’s functionality and performance.


Vulnerability Description

1. Unbounded pendingNFTs Mapping

The contract stores NFTs that have been partially paid for but not yet fully purchased in a pendingNFTs mapping. There is no limit on the number of entries that can be stored in this mapping. This allows a malicious user to repeatedly trigger the "trick" scenario (by only sending the initial portion of the required ETH), resulting in an indefinite number of entries in the pendingNFTs mapping. This behavior can quickly consume the contract's storage capacity, potentially leading to higher gas costs for any subsequent operations involving the contract.

2. Absence of Duplicate Entry Check

The contract does not implement a mechanism to prevent duplicate entries for the same user and treat combination within the pendingNFTs mapping. This can result in multiple entries being created for the same user, leading to inconsistencies, unintended behavior, and further strain on the contract’s storage.

3. Lack of Expiration Mechanism for Pending NFTs

Once an NFT is stored in the pendingNFTs mapping as a pending transaction, there is no expiration or timeout mechanism. This means that if a user fails to complete the transaction (by calling the resolveTrick function), the entry remains indefinitely in the contract’s storage. This could result in persistent storage bloat over time, increasing the risk of DoS due to storage exhaustion.


Vulnerable Code

The following code snippet from the trickOrTreat function illustrates the issue:

else {
// User didn't send enough ETH
// Mint NFT to contract and store pending purchase
uint256 tokenId = nextTokenId;
_mint(address(this), tokenId);
_setTokenURI(tokenId, treat.metadataURI);
nextTokenId += 1;
pendingNFTs[tokenId] = msg.sender;
pendingNFTsAmountPaid[tokenId] = msg.value;
tokenIdToTreatName[tokenId] = _treatName;
emit Swapped(msg.sender, _treatName, tokenId);
// User needs to call fellForTrick() to finish the transaction
}

Issues in the Code:

  • Unbounded Mapping: No constraints on the number of pendingNFTs entries.

  • Missing Duplicate Checks: No validation to ensure that a user does not have multiple pending entries for the same treat.

  • No Expiration Mechanism: Entries in pendingNFTs remain until the user completes the purchase manually.


Proof of Concept

The following test script demonstrates how an attacker could exploit the vulnerability to perform a denial-of-service attack

function testTrickOrTreatDoublePrice1000() public {
for (uint256 i = 0; i < 100000; i++) {
console2.log("Iteration:", i + 1); // Log the iteration number
// Fund the user with enough ether
vm.deal(user, 1000 ether);
vm.startPrank(user);
uint256 singlePrice = 0.01 ether;
swap.trickOrTreat{value: singlePrice}("Candy");
console2.log("Contract Balance", address(swap).balance);
uint256 TokenID = 2+i;
assertEq(swap.getNextTokenID(),TokenID, "Next token ID should increment after mint.");
address pendingNFTAddr = swap.getPendingNFT(1);
console2.log("Pending NFT Address", pendingNFTAddr);
assertEq(pendingNFTAddr, user);
vm.stopPrank();
}
}

Impact

  1. Denial of Service (DoS)

An attacker can exploit the absence of restrictions on the pendingNFTs mapping to create numerous pending entries, effectively filling up the contract's storage space. This can slow down or prevent legitimate users from interacting with the contract, making the trickOrTreat function and potentially the entire contract unusable.

  1. Storage Bloat

Due to the lack of a limit on pending entries and the absence of an expiration mechanism, the pendingNFTs mapping can grow uncontrollably. This can significantly increase the gas costs for any transactions interacting with the contract, potentially leading to prohibitive costs for users and making the contract economically unfeasible to use.

Updates

Appeal created

bube Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

[invalid] Unlimited pending NFTs

The protocol can work correctly with more than 20000 tokens in it. It is informational.

Support

FAQs

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