Snowman Merkle Airdrop

AI First Flight #10
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: high
Likelihood: medium
Invalid

Signature Replay Vulnerability Allows Multiple NFT Claims

Root + Impact

Description

  • The SnowmanAirdrop contract uses ECDSA signature validation to authorize NFT claims through the _isValidSignature() function

  • The signature validation lacks replay protection mechanisms such as nonce tracking or signature hash recording, allowing attackers to reuse valid signatures multiple times across different transactions

// SnowmanAirdrop.sol:102-109
function _isValidSignature(address receiver, bytes32 digest, uint8 v, bytes32 r, bytes32 s)
internal
pure
returns (bool)
{
@> (address actualSigner,,) = ECDSA.tryRecover(digest, v, r, s);
@> return actualSigner == receiver;
}

Risk

Likelihood:

  • Valid signatures remain usable indefinitely after the first claim transaction completes

  • Attackers intercept valid signature data from blockchain transaction history or mempool monitoring

Impact:

  • Attackers replay valid signatures to claim multiple NFTs beyond their entitled allocation

  • The airdrop distribution becomes unfair as malicious actors drain the NFT pool through signature replay attacks

Proof of Concept

The attack exploits the lack of signature replay protection by reusing valid signatures from previous transactions. An attacker monitors the blockchain for successful claim transactions, extracts the signature parameters, and replays them to claim additional NFTs.

Attack Flow:

  1. Legitimate user (Alice) claims their airdrop NFT using signature (v, r, s)

  2. Transaction succeeds and is recorded on the blockchain

  3. Attacker monitors Etherscan or blockchain APIs for claimSnowman transactions

  4. Attacker extracts signature parameters (v, r, s) from Alice's transaction calldata

  5. Attacker deploys SignatureReplayAttack contract

  6. Attacker calls replayAttack() with Alice's credentials

  7. The contract calls claimSnowman() three times with the same signature

  8. All three calls succeed because the signature is never invalidated

  9. Attacker receives 3 NFTs using Alice's signature

// Attacker monitors the blockchain for valid signature data
contract SignatureReplayAttack {
SnowmanAirdrop public airdrop;
constructor(address _airdrop) {
airdrop = SnowmanAirdrop(_airdrop);
}
function replayAttack(
address receiver,
bytes32[] calldata merkleProof,
uint8 v,
bytes32 r,
bytes32 s
) external {
// Transaction 1: Legitimate user claims NFT with signature (v, r, s)
// Attacker observes the signature from tx history
// Transaction 2: Attacker replays the same signature multiple times
airdrop.claimSnowman(receiver, merkleProof, v, r, s);
airdrop.claimSnowman(receiver, merkleProof, v, r, s);
airdrop.claimSnowman(receiver, merkleProof, v, r, s);
// Can be called unlimited times with the same signature
}
}
// Example: Attacker checks recent transactions on Etherscan
// Extracts signature parameters (v, r, s) from successful claim
// Replays the signature in new transactions to claim more NFTs

Expected Result: The attacker successfully replays one signature to claim multiple NFTs. If 100 users have claimed their NFTs, an attacker can extract all 100 signatures and claim 100+ additional NFTs, doubling the total supply and depriving legitimate future claimants.

Recommended Mitigation

+ // Add mapping to track used signatures
+ mapping(bytes32 => bool) private usedSignatures;
function claimSnowman(
address receiver,
bytes32[] calldata merkleProof,
uint8 v,
bytes32 r,
bytes32 s
)
external
{
+ bytes32 signatureHash = keccak256(abi.encodePacked(v, r, s));
+ require(!usedSignatures[signatureHash], "Signature already used");
+ usedSignatures[signatureHash] = true;
+
// ... existing validation code ...
}

Add a mapping to track used signatures by their hash. Check if a signature has been used before processing the claim, and mark it as used after successful validation. This prevents signature replay attacks by ensuring each signature can only be used once.

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!