Snowman Merkle Airdrop

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

[H-01] Missing Signature Verification in _isValidSignature allows Unauthorized Airdrop Claims

Root + Impact

Description

Description

  • Normal Behavior: The protocol requires a valid ECDSA signature from the receiver to authorize the airdrop claim. This ensures that only the rightful owner (or an authorized delegate) can trigger the minting process.

  • Specific Issue: The internal function _isValidSignature calls ECDSA.tryRecover but fails to verify if the recovered address matches the receiver. The function returns the default state or ignores the result, allowing any arbitrary signature (or even dummy values for v, r, s) to pass the check and proceed to minting.

// src/SnowmanAirdrop.sol
function _isValidSignature(address receiver, bytes32 digest, uint8 v, bytes32 r, bytes32 s)
internal
pure
returns (bool)
{
// @> VULNERABILITY: The recovered address is never compared to 'receiver'
// @> Slither identified this as an unused return value.
(address actualSigner, , ) = ECDSA.tryRecover(digest, v, r, s);
// The function logic ends here without returning 'actualSigner == receiver'
}

Risk

Likelihood

  • High: As proven in the Foundry test testFuzz_SignatureBypass, any random values for v, r, s are accepted by the contract, making the exploit accessible to anyone with zero cryptographic knowledge.

Impact:

  • High/Critical: This vulnerability breaks the primary access control of the airdrop. An attacker can claim Snowman NFTs for any eligible address in the Merkle Tree, effectively "stealing" the claim rights from legitimate users.

Proof of Concept

function testFuzz_SignatureBypass(uint8 v, bytes32 r, bytes32 s) public {
vm.prank(user);
// The call succeeds or fails at the Merkle stage, NOT the signature stage.
try airdrop.claimSnowman(user, new bytes32[](0), v, r, s) {
// Successful bypass
} catch (bytes memory reason) {
// Reached internal logic despite invalid v, r, s
}
}

Recommended Mitigation

function _isValidSignature(address receiver, bytes32 digest, uint8 v, bytes32 r, bytes32 s)
internal
pure
returns (bool)
{
- (address actualSigner, , ) = ECDSA.tryRecover(digest, v, r, s);
+ (address actualSigner, ECDSA.RecoverError error, ) = ECDSA.tryRecover(digest, v, r, s);
+ if (error != ECDSA.RecoverError.NoError) return false;
+ return actualSigner == receiver;
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 4 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!