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

Address used for leaf creation in `MerkleAirdrop::claim` function is directly from parameter which can lead to caller claiming for any other valid address.

Summary

Any caller can claim airdrop for any valid address that is part of Merkle tree.

Vulnerability Details

Function claim has address account parameter which is directly used in leaf creation. This means any caller can claim airdrop for any valid address that is part of Merkle tree. Caller will need to pay fee. Amount will be transferred to account parameter which may not be desired behavior and can be malicious.

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

Impact

Malicious caller could claim for valid address that is currently not expecting to receive any USDC.

zkSync Era comes with native account abstraction support for private-key controlled EOAs. So there is possibility that receiver has contract logic which could mishandle received USDC, so there is possibility that USDC will be lost permanently.

Proof of Concept

  1. Random caller has enough Ether to cover fee.

  2. Random caller calls claim function with collectorOne address and amount to claim.

  3. collectorOne receives intended amount.

Place the following test into MerkleAirdropTest.t.sol.

function testClaimForOtherAddress() public {
uint256 startingBalance = token.balanceOf(collectorOne);
address randomCaller = address(452);
vm.deal(randomCaller, airdrop.getFee());
vm.startPrank(randomCaller);
airdrop.claim{value: airdrop.getFee()}(collectorOne, amountToCollect, proof);
vm.stopPrank();
uint256 endingBalance = token.balanceOf(collectorOne);
assertEq(endingBalance - startingBalance, amountToCollect);
}

Tools Used

Manual review

Recommendations

Suggestion is to not allow account parameter in claim function, but to use msg.sender instead so caller can only claim for themselves.

- 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 about 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.