Normal Protocol Behavior: The Snowman.sol
contract (ERC721) NFTs are intended to be distributed via the SnowmanAirdrop.sol
contract. This airdrop contract uses a Merkle tree system. Recipients prove their eligibility, stake their Snow
tokens into the SnowmanAirdrop.sol
contract, and in return, the SnowmanAirdrop.sol
contract is responsible for ensuring they receive Snowman NFTs equal to their staked Snow
balance. This controlled mechanism is crucial for the NFT's intended distribution and value.
Specific Vulnerability: The mintSnowman(address receiver, uint256 amount)
function in Snowman.sol
is declared external
and lacks any access control. This allows any external account to call it directly and mint an arbitrary number of Snowman NFTs to any address, completely bypassing the Snow
token staking and Merkle verification process managed by SnowmanAirdrop.sol
.
Likelihood: High
An attacker (any external account) can easily discover and call the public mintSnowman
function.
Impact: High
Unlimited NFT Supply & Devaluation: Attackers can mint an unlimited number of Snowman NFTs, destroying their scarcity and value.
Circumvention of Airdrop Mechanism: The intended distribution via SnowmanAirdrop.sol
(based on Merkle proofs and Snow
token staking) is rendered ineffective.
Unfair Advantage and Market Manipulation: Attackers can pre-mint NFTs.
Loss of User Trust and Project Credibility: This fundamental flaw damages the project's reputation.
The following Foundry test, testVulnerability_AttackerCanMintSnowman
from test/Snowman.t.sol
, demonstrates that an arbitrary attacker
address can successfully call mintSnowman
.
To align with the protocol's described invariants, where SnowmanAirdrop.sol
manages the eligibility and distribution of Snowman NFTs based on Snow
token staking and Merkle proofs, the mintSnowman
function in Snowman.sol
must be restricted.
The most direct and secure approach is to designate SnowmanAirdrop.sol
as the exclusive minter for Snowman.sol
.
Modify Snowman.sol
to implement a "Minter Role":
Add a state variable to store the address of the authorized minterContract
.
Add a modifier (onlyMinter
) that restricts the execution of mintSnowman
to this minterContract
address.
Add an onlyOwner
function (setMinter
) to allow the owner of Snowman.sol
to set the address of the SnowmanAirdrop.sol
contract as the minterContract
.
Operational Flow:
After deploying both Snowman.sol
and SnowmanAirdrop.sol
, the owner of Snowman.sol
calls setMinter(addressOfSnowmanAirdropContract)
on Snowman.sol
.
When a user (recipient) successfully calls claimSnowman
(or claimSnowmanFor
) in SnowmanAirdrop.sol
:
SnowmanAirdrop.sol
verifies the Merkle proof and signatures (if any).
SnowmanAirdrop.sol
handles the staking of the user's Snow
tokens.
SnowmanAirdrop.sol
then calls Snowman.sol::mintSnowman(recipient, verifiedAmount)
, where verifiedAmount
is the number of NFTs the recipient is entitled to, based on their Snow
balance from the Merkle proof.
This mitigation ensures:
The mintSnowman
function in Snowman.sol
is no longer publicly callable.
Only SnowmanAirdrop.sol
, after its internal verifications (Merkle proof, Snow staking), can trigger the minting of Snowman NFTs.
This directly implements the README's intent: "Recipients stake their Snow tokens and receive Snowman NFTS equal to their Snow balance in return" via the SnowmanAirdrop
contract.
The mint function of the Snowman contract is unprotected. Hence, anyone can call it and mint NFTs without necessarily partaking in the airdrop.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.