Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

[M-4] Unsafe `safeTransferFrom` Usage in `claimSnowman`

Root + Impact


  • Root: claimSnowman uses safeTransferFrom with receiver as from instead of msg.sender;

  • Impact: Allows attackers to burn tokens from arbitrary addresses they control.

Description

  • The function transfers Snow from receiver, enabling the caller to specify a victim’s address, bypassing msg.sender authorization.

Risk

// Root cause in the codebase with @> marks to highlight the relevant sectionfunction claimSnowman(address receiver, bytes32[] calldata merkleProof, uint8 v, bytes32 r, bytes32 s)
external
nonReentrant
{
if (receiver <= address(0)) {
revert SA__ZeroAddress();
}
if (i_snow.balanceOf(receiver) <= 0) {
revert SA__ZeroAmount();
}
if (!_isValidSignature(receiver, getMessageHash(receiver), v, r, s)) {
revert SA__InvalidSignature();
}
uint256 amount = i_snow.balanceOf(receiver);
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(receiver, amount))));
if (!MerkleProof.verify(merkleProof, i_merkleRoot, leaf)) {
revert SA__InvalidProof();
}
@> i_snow.safeTransferFrom(receiver, address(this), amount); // Uses receiver as from
s_hasClaimedSnowman[receiver] = true;
emit SnowmanClaimedSuccessfully(receiver, amount);
i_snowman.mintSnowman(receiver, amount);
}

Risk

Likelihood:

  • During airdrop execution with a maliciously chosen receiver address.

  • When an attacker has approval to transfer tokens from a victim’s address.

Impact:

  • Unauthorized burning of tokens from external addresses.

  • Potential financial loss for affected token holders.

Proof of Concept

Recommended Mitigation

function claimSnowman(address receiver, bytes32[] calldata merkleProof, uint8 v, bytes32 r, bytes32 s)
external
nonReentrant
{
if (receiver <= address(0)) {
revert SA__ZeroAddress();
}
if (i_snow.balanceOf(msg.sender) <= 0) {
revert SA__ZeroAmount();
}
if (s_hasClaimedSnowman[receiver]) {
revert SA__AlreadyClaimed();
}
- i_snow.safeTransferFrom(receiver, address(this), amount);
+ i_snow.safeTransferFrom(msg.sender, address(this), amount);
s_hasClaimedSnowman[receiver] = true;
emit SnowmanClaimedSuccessfully(receiver, amount);
i_snowman.mintSnowman(receiver, amount);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 21 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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