AirDropper

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

claim() has no msg.sender binding: anyone can force-claim for any eligible account, enabling fee-griefing and unwanted execution

claim() does not bind msg.sender to account, letting anyone trigger any recipient's claim and amplify the replay drain

Description

claim() accepts account as a parameter rather than deriving it from msg.sender. Anyone can call the function for any eligible account by paying FEE, and the tokens are sent to account (not the caller).

function claim(address account, uint256 amount, bytes32[] calldata merkleProof) external payable {
if (msg.value != FEE) { // @> anyone can pay FEE and trigger account's claim
revert MerkleAirdrop__InvalidFeeAmount();
}
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount))));

(src/MerkleAirdrop.sol:30-34)

Risk

Likelihood: Low

Calling on someone else's behalf is permissionless but does not redirect funds, so there is little direct incentive in isolation. The realistic abuse is griefing or chaining with the replay bug.

Impact: Low

A griefer cannot steal tokens this way (they go to account), but they can force execution timing on a victim's claim, burn their FEE value, and, combined with the missing replay guard (AD-01), an attacker can drive the unbounded re-claim loop against any leaf rather than only their own, broadening the drain surface.

Proof of Concept

A third party with no allocation pays the fee and successfully drives another account's claim.

function test_anyoneCanClaimForAccount() public {
vm.prank(stranger);
airdrop.claim{value: airdrop.getFee()}(account, amount, proof); // stranger != account, still succeeds
assertEq(token.balanceOf(account), amount);
}

Recommended Mitigation

Bind the caller to the recipient, or explicitly adopt a relayer model where the recipient is paid and pays nothing.

- function claim(address account, uint256 amount, bytes32[] calldata merkleProof) external payable {
+ function claim(uint256 amount, bytes32[] calldata merkleProof) external payable {
+ address account = msg.sender;
if (msg.value != FEE) {
revert MerkleAirdrop__InvalidFeeAmount();
}
Updates

Lead Judging Commences

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