AirDropper

AI First Flight #5
Beginner FriendlyDeFiFoundry
EXP
View results
Submission Details
Impact: low
Likelihood: medium
Invalid

Claim function accepts arbitrary account parameter enabling unauthorized claim triggers

Root + Impact

Description

  • The claim() function accepts an account parameter as the token recipient but never validates it against msg.sender. Any caller can trigger a claim on behalf of any eligible address.

  • Under normal behavior, each user should claim their own allocation. The account parameter should either be removed or validated.

  • The issue is that account is untrusted. Any external caller can pass any valid address and proof combination. The tokens go to the specified account, not to msg.sender.

// Root cause in the codebase with => marks to highlight the relevant section
function claim(address account, uint256 amount, bytes32[] calldata merkleProof) external payable {
...
// => account is never validated against msg.sender
i_airdropToken.safeTransfer(account, amount);
}

Risk

Likelihood:

  • Reason 1 // Any external caller can invoke claim() with a valid proof and an eligible account parameter, regardless of who owns that account.

  • Reason 2 // If combined with a claim-tracking fix, this enables griefing where an attacker triggers a victim's claim, preventing the victim from claiming at a time of their own choosing.

Impact:

  • Impact 1 // Griefing: An attacker can front-run a legitimate user's claim, causing the victim to receive tokens at an unexpected time.

  • Impact 2 // With claim tracking added, a malicious actor can permanently lock a victim's allocation by claiming on their behalf.

Proof of Concept

function testFinding_AnyoneCanClaimForAnyAddress() public {
vm.deal(attacker, airdrop.getFee());
uint256 collectorBalanceBefore = token.balanceOf(collectorOne);
vm.prank(attacker);
airdrop.claim{ value: airdrop.getFee() }(collectorOne, amountToCollect, proof);
assertEq(token.balanceOf(collectorOne) - collectorBalanceBefore, amountToCollect);
assertEq(token.balanceOf(attacker), 0, "attacker paid fee but received no tokens");
}

Recommended Mitigation

- 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))));
...
- i_airdropToken.safeTransfer(account, amount);
+ i_airdropToken.safeTransfer(msg.sender, amount);
}
Updates

Lead Judging Commences

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