Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Valid

Critical: Universal NFT Counterfeit Vulnerability in mintSnowman() - Unlimited Unauthorized Minting & Collection Devaluation

Root + Impact

Description

  • Normal behavior:
    The mintSnowman() function should only be callable by the authorized SnowmanAirdrop contract to distribute NFTs based on verified airdrop claims.

  • Issue:
    The mintSnowman() function lacks any access control, allowing any address to call it and mint unlimited NFTs to any recipient. This completely bypasses the airdrop verification mechanism and destroys the value and trust of the NFT distribution process.

// >>> Root cause: Public mint function lacks access control, enabling unrestricted NFT minting @>
function mintSnowman(address receiver, uint256 amount) external {
// @> No access control: anyone can mint
for (uint256 i = 0; i < amount; i++) {
_safeMint(receiver, s_TokenCounter); // @> Arbitrary minting
s_TokenCounter++;
}
}

Risk

Likelihood:

  • Medium — Any user who knows the contract address can interact with it directly.

  • Reproducibility: Always exploitable without restriction.

  • Ease of exploitation: Requires only a basic transaction to call the function.

Impact:

  • Unlimited NFT minting by anyone.

  • Complete devaluation of the Snowman NFT collection.

  • Bypassing of protocol logic around airdrops, eligibility, and rarity.

  • Loss of community trust, protocol integrity, and NFT holder value.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "../src/Snowman.sol";
contract SnowmanExploit is Test {
Snowman snowman;
address attacker = address(0x666);
function testUnauthorizedMint() public {
snowman = new Snowman("ipfs://Qm...", address(this)); // Malicious actor deploys
snowman.mintSnowman(attacker, 10_000); // Unauthorized mint
assertEq(snowman.balanceOf(attacker), 10_000);
}
}

Explanation:

  • An attacker or any public user can call mintSnowman() directly.

  • They mint thousands of NFTs to any address without verification or restriction.

  • This bypasses the airdrop reward logic and damages the collection's credibility.


Recommended Mitigation

Restrict minting access to the authorized airdrop contract only. This enforces the protocol’s reward flow integrity and ensures NFTs are only issued after valid claim processes.

+ address private immutable i_minterAddress;
- constructor(string memory _SnowmanSvgUri) {
+ constructor(string memory _SnowmanSvgUri, address minter) {
+ i_minterAddress = minter;
// initialization logic
}
function mintSnowman(address receiver, uint256 amount) external {
+ require(msg.sender == i_minterAddress, "Unauthorized");
for (uint256 i = 0; i < amount; i++) {
_safeMint(receiver, s_TokenCounter);
s_TokenCounter++;
}
}

Explanation:

  • Solution: Introduces immutable i_minterAddress during deployment.

  • Security: Only the authorized SnowmanAirdrop contract can call mintSnowman().

  • Efficiency: Minimal runtime overhead added.

  • Compatibility: No breaking changes; integrates cleanly with deployment workflow.

Severity Note:

This is a critical vulnerability because it allows uncontrolled supply inflation and destroys NFT value. The absence of access control breaks protocol guarantees and allows malicious or accidental misuse of minting functionality.

Verification confirms proper functionality:

function testMintRestrictedToAirdrop() public {
vm.prank(address(0x123)); // Not the airdrop contract
vm.expectRevert("Unauthorized");
snowman.mintSnowman(address(0x456), 1);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Unrestricted NFT mint function

The mint function of the Snowman contract is unprotected. Hence, anyone can call it and mint NFTs without necessarily partaking in the airdrop.

Support

FAQs

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