🍃Airdrop Vulnerability – Merkle Leaves and Parent Node Hash Collisions
Merkle trees are commonly used in blockchain applications, particularly in airdrop contracts, to efficiently verify users' eligibility to claim tokens. By generating a Merkle root and providing users with Merkle proofs, projects can ensure secure and scalable token distribution.
However, a vulnerability can arise when Merkle leaves (individual user data, such as wallet addresses and token amounts) and parent nodes (hashes of child nodes) are the same size. This creates a potential for hash collisions, where a leaf can be confused with a parent node, allowing an attacker to claim tokens fraudulently. In the context of airdrops, this can lead to malicious users claiming tokens they are not entitled to.
The Vulnerability: Hash Collisions Between Merkle Tree Leaves and Parent Nodes in Airdrops
In a typical airdrop, a Merkle tree is used to verify that a user’s address and allocated token amount exist in the Merkle root (the root hash of the tree). Each user is assigned a Merkle proof, which contains sibling node hashes used to verify the path from the user’s leaf node to the root.
Here’s how a Merkle tree-based airdrop works:
Leaf Node: Each user’s address and token amount are hashed to create a leaf in the Merkle tree.
Parent Node: Pairs of leaf nodes are hashed together to form parent nodes, and this process continues until a single root hash (the Merkle root) is generated.
However, if the size of the leaf (which contains user-specific data like their address and token allocation) and the size of the parent node are the same, it opens up a serious security risk. The attacker can construct a malicious leaf that resembles a parent node, enabling them to provide a fraudulent proof and claim tokens multiple times.
Example of the Vulnerability:
Let’s examine a simple Merkle tree structure used for an airdrop:
The leaves
d, e, f, g
are individual users’ address-token pair hashes.b = keccak(d || e)
andc = keccak(f || g)
are parent hashes, whilea = keccak(b || c)
is the Merkle root.
The issue arises when leaf nodes and parent nodes are the same size (e.g., both 64 bytes). In a typical airdrop implementation, a leaf node might be calculated as follows:
In this case, abi.encode(address, uint)
outputs 64 bytes. Since parent nodes are also generated by hashing 64-byte values (the hashes of two child nodes), it becomes possible for an attacker to generate a leaf node that collides with a parent node. This enables the attacker to fraudulently claim tokens.
How an Attacker Can Exploit the Vulnerability:
Create a Fraudulent Leaf: The attacker sets the
userAddress
to a hash that matches a parent node and setstokenAmount
to another hash. This results in a fraudulent leaf that mimics a valid node in the Merkle tree.Submit a Fraudulent Proof: The attacker submits this fraudulent leaf with an empty Merkle proof, claiming that they are already at the root of the tree. This bypasses the need for a valid proof, allowing them to claim tokens without having a legitimate allocation.
Repeat the Attack: Since this fraudulent proof is treated as valid by the contract, the attacker can repeat the process, claiming multiple allocations of tokens.
Practical Example:
Consider a scenario where an attacker generates a fake leaf for an airdrop. They set userAddress
to keccak(d || e)
and tokenAmount
to keccak(f || g)
:
They submit this with an empty proof, and the contract accepts it as valid because the size of the leaf matches the parent node's hash. This allows them to fraudulently claim tokens.
Impact: Exploiting the Airdrop
In an airdrop context, this vulnerability allows attackers to:
Claim Tokens Multiple Times: By submitting fraudulent proofs, an attacker can drain the airdrop contract by claiming tokens they are not entitled to.
Disrupt Fair Token Distribution: Since airdrops are typically designed to distribute tokens to a large set of users, a hash collision exploit can disrupt the distribution process, reducing the amount of tokens available for legitimate users.
Drain the Contract: If exploited at scale, this vulnerability can lead to the attacker draining the entire airdrop contract, leaving no tokens for legitimate claimants.
Mitigation: Preventing Hash Collisions in Airdrop Contracts
To prevent attackers from exploiting hash collisions between leaf and parent nodes in a Merkle tree, the solution is to reduce the size of the data being hashed for leaf nodes. This can be done by using abi.encodePacked()
instead of abi.encode()
for leaf hashes, ensuring that the leaf nodes are smaller than parent nodes.
1. Use abi.encodePacked()
for Leaves
Modify the leaf node hashing function to use abi.encodePacked()
instead of abi.encode()
:
This reduces the size of the leaf data being hashed from 64 bytes to 52 bytes (20 bytes for the address and 32 bytes for the token amount), which prevents collisions with 64-byte parent nodes.
2. Ensure Distinct Data Sizes for Leaves and Parent Nodes
By using abi.encodePacked()
for leaf nodes, you ensure that leaves and parent nodes are hashed from different lengths of data. Since keccak256
is resistant to length extension attacks, the different lengths of data ensure that leaves and parent nodes will never produce the same hash.
3. Implement Proper Validation Checks
Ensure that the airdrop contract has proper validation checks for Merkle proofs and includes bounds on the length of proofs provided by users. This can prevent attackers from submitting fraudulent empty proofs to bypass the tree structure.
Conclusion
Merkle trees are powerful tools for secure token distribution in airdrops, but they must be implemented correctly to avoid security vulnerabilities. When leaf nodes and parent nodes are the same size, attackers can exploit hash collisions to claim tokens they are not entitled to, disrupting the airdrop process.
By reducing the size of leaf nodes using abi.encodePacked()
, ensuring proper validation, and implementing distinct hashing mechanisms, developers can protect their airdrop contracts from this type of vulnerability, ensuring fair and secure token distribution.
Last updated