Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
Submission Details
Impact: high
Likelihood: high

Missing access control on mintSnowman

Author Revealed upon completion

Root + Impact

Unrestricted minting allows anyone to mint NFTs without staking requirements

Description

The Snowman contract exposes an external function mintSnowman(address receiver, uint256 amount) that allows arbitrary external callers to mint NFTs without any form of access control or staking verification.

According to the intended logic, only users who have staked the Snow token should be eligible to receive Snowman NFTs. However, in the current form, the function permits any address to mint any quantity of NFTs to any recipient, completely bypassing the staking requirements.

This behavior violates business logic expectations and undermines the integrity of the NFT collection.

Risk

Likelihood: High

The function is explicitly marked external and lacks any form of access control. There are no checks to verify the caller has staked Snow tokens or is an authorized distributor. As a result, it is trivial for any contract or EOA to mint NFTs arbitrarily.

Impact: High

This flaw completely breaks the staking-to-NFT minting mechanism and devalues the collection. It allows malicious actors to:

  • Mint unlimited NFTs for themselves or others without meeting the staking criteria.

  • Disrupt the airdrop distribution logic relying on staking validation.

  • Harm protocol reputation and economically dilute rewards from legitimate stakers.

Proof of Concept

An attacker can easily exploit this function by deploying a simple contract or interacting directly via a frontend or RPC:

contract Attacker {
Snowman public target;
constructor(address _target) {
target = Snowman(_target);
}
function attack() public {
// Mint 1000 NFTs to attacker
target.mintSnowman(msg.sender, 1000);
}
}

Alternatively, a user could call the function directly via Etherscan or any web3 interface:

await snowman.mintSnowman(attackerAddress, 1000);

Recommended Mitigation

Restrict access to the mintSnowman function to prevent arbitrary calls. The function should only be callable by a trusted contract (e.g., SnowmanAirdrop) that has verified user eligibility based on staking or protocol rules.

Option 1: Use Ownable Access Control

Mark the function with onlyOwner, and transfer ownership to the authorized minting contract (e.g., SnowmanAirdrop):

- function mintSnowman(address receiver, uint256 amount) external {
+ function mintSnowman(address receiver, uint256 amount) external onlyOwner {
// minting logic
}

Then, in your deployment or initialization sequence:

snowman.transferOwnership(snowmanAirdropAddress);

Option 2: Use a dedicated onlyMinter modifier

Introduce a minter role or address and restrict calls to that entity only, allowing more granular control than Ownable.

address public minter;
modifier onlyMinter() {
require(msg.sender == minter, "Not authorized to mint");
_;
}
function setMinter(address _minter) external onlyOwner {
minter = _minter;
}
function mintSnowman(address receiver, uint256 amount) external onlyMinter {
// minting logic
}

Implementing proper access control ensures that the NFT minting process remains compliant with the intended staking logic and prevents abuse that could erode trust in the protocol.

Support

FAQs

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