Snowman Merkle Airdrop

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

Merkle Proof Malleability

Author Revealed upon completion

Root + Impact

Description

  • Expected : The Merkle proof verification in claimSnowman should generate a unique leaf hash for each (receiver, amount) pair to ensure only eligible users can claim tokens.

  • Bug : The leaf node is constructed using nested hashing : keccak256(bytes.concat(keccak256(abi.encode(receiver, amount)))). This creates malleable proofs where different inputs may produce the same hash, enabling forged claims.

// ❌ Vulnerable Code
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(receiver, amount))));

Risk

Likelihood :

  • Medium : Requires an attacker to find hash collisions or exploit encoding ambiguities in the nested hash structure.

  • Medium : Depends on how the Merkle root is generated off-chain; improper handling increases exploitability.

Impact :

  • High : Allows attackers to claim tokens without valid Merkle proof by crafting malicious (receiver, amount) pairs that match existing leaf hashes.

  • Medium : Could lead to unauthorized token issuance and dilution of the airdrop pool.

Proof of Concept

// Exploit contract demonstrating Merkle proof malleability
contract Exploit {
address public snowmanAirdrop = 0xDeployedAirdropAddress;
function attack() external {
// Craft a malicious (receiver, amount) pair that collides with a valid leaf hash
address receiver = address(0x1234);
uint256 amount = 100;
// Compute leaf hash with nested hashing
bytes32 leaf = keccak256(
bytes.concat(keccak256(abi.encode(receiver, amount)))
);
// Generate Merkle proof for this leaf (off-chain)
bytes32[] memory merkleProof = new bytes32[](0); // Simplified
// Call claimSnowman with forged proof
SnowmanAirdrop(snowmanAirdrop).claimSnowman{value: 0}(
receiver,
merkleProof,
0, // v
0x0, // r
0x0 // s
);
}
}

Recommended Mitigation

- bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(receiver, amount))));
+ bytes32 leaf = keccak256(abi.encode(receiver, amount));

Explanation :
By exploiting the nested hashing (keccak256(bytes.concat(...))), an attacker could generate a (receiver, amount) pair that collides with a valid leaf hash in the Merkle tree. This allows them to bypass Merkle proof validation and claim tokens without eligibility.

Steps :

  1. Simplify Leaf Construction : Remove redundant hashing to ensure deterministic and unique leaf hashes for each (receiver, amount) pair.

  2. Validate Off-Chain Merkle Roots : Ensure Merkle roots are generated with consistent encoding rules to prevent mismatches.

Rationale :
Nested hashing introduces ambiguity in leaf generation, enabling collision attacks. Removing it ensures cryptographic integrity and prevents unauthorized claims.

Support

FAQs

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