Beginner FriendlyDeFiFoundry
100 EXP
View results
Submission Details
Severity: high
Invalid

Anyone can submit a claim on behalf of any address, and anyone can precompute Merkle proofs

Summary

MerkleAirdrop::claim does not check whether input parameter account is the same address as msg.sender. This flaw allows any caller to claim airdrops for any address if they have the corresponding Merkle proof.

The risk is exacerbated by the public knowledge of eligible addresses and amounts, enabling attackers to precompute Merkle proofs and claim airdrops fraudulently.

Vulnerability Details

MerkleAirdrop::claim is supposed to enable airdrop-eligible users to claim their portion of the airdrop. However, the function verifies eligibility using the account parameter instead of the address msg.sender, and fails to ensure that these addresses match. While zkSync does not have a public mempool which makes traditional front-running less of a concern, the platform’s operators do possess the ability to see transactions before they are executed, allowing them to reorder transactions and claim before the eligible user.

To make things worse, the list of eligible addresses and their respective amounts are public data and, hence, an attacker can independently generate the necessary proofs without needing to intercept a specific transaction. This vulnerability could be exploited systematically, with bots programmed to claim all available airdrops before the rightful recipients.

The following test demonstrates that if a malicious, non-airdrop-eligible user acquires the proof and address of an airdrop-eligible user (either by precomputing the proofs from publicly available data, or by observing claim transactions in the mempool), then they can submit their own claim transaction with the these inputs, and can succesfully claim (by front-running the eligible user):

Proof of Code
function testAnyUserCanClaim() public {
address user = makeAddr("user");
uint256 startingBalance = token.balanceOf(user);
vm.deal(user, airdrop.getFee());
vm.startPrank(user);
// Here, it is assumed that 'user' knows the address and proof of an eligible user.
airdrop.claim{ value: airdrop.getFee() }(collectorOne, amountToCollect, proof);
vm.stopPrank();
uint256 endingBalance = token.balanceOf(collectorOne);
assertEq(endingBalance - startingBalance, amountToCollect);
}

Impact

Malicious, non-airdrop-eligible users can claim the airdrop reserved for airdrop-eligible users. To exploit this vulnerability, attackers can:

  • reconstruct the Merkle tree using publicly available data, including the list of eligible addresses and their corresponding amounts, enabling them to generate the necessary proofs to submit fraudulent claims;

  • leverage their position as operators within zkSync to observe and intercept valid claim transactions from eligible users, allowing them to submit these transactions on their behalf and claim the airdrops before the legitimate recipients."

Tools Used

Manual review, Foundry.

Recommendations

Perform the eligibility verification on address msg.sender, not on MerkleAirdrop::claim input parameter account. To do this, modify MerkleAirdrop as follows:

- function claim(address account, uint256 amount, bytes32[] calldata merkleProof) external payable {
+ function claim(uint256 amount, bytes32[] calldata merkleProof) external payable {
if (msg.value != FEE) {
revert MerkleAirdrop__InvalidFeeAmount();
}
- bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount))));
+ bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, amount))));
if (!MerkleProof.verify(merkleProof, i_merkleRoot, leaf)) {
revert MerkleAirdrop__InvalidProof();
}
- emit Claimed(account, amount);
+ emit Claimed(msg.sender, amount);
- i_airdropToken.safeTransfer(account, amount);
+ i_airdropToken.safeTransfer(msg.sender, amount);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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