Mystery Box

First Flight #25
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

[H-2] Potential Reentrancy Vulnerability in MysteryBox::claimSingleReward Function

Summary

The claimSingleReward function in the MysteryBox contract allows users to claim rewards by sending Ether back to their address. However, this function is susceptible to reentrancy attacks because it first interacts with an external contract (the user's address) by sending Ether before updating the internal state. This could allow a malicious user to repeatedly call the claimSingleReward function before the state changes are completed, potentially draining the contract's funds.

Vulnerability Details

The claimSingleReward function allows a user to claim a single reward by specifying an index. It checks that the index is valid and that the reward has a non-zero value before transferring the Ether.

Steps to Exploit

  • The function calculates the reward value and checks its validity.

  • It sends the Ether to the user via a low-level call (call{value: value}("")).

  • Finally, it deletes the claimed reward from the rewardsOwned mapping.

  • If a user’s fallback function is triggered during the Ether transfer (e.g., by calling claimSingleReward again), it could allow them to claim the same reward multiple times, as the deletion of the reward occurs after the transfer.

Impact

A successful reentrancy attack will allow an attacker to repeatedly invoke the claimSingleReward function before the contract state is updated. This can lead to the attacker draining a significant amount of Ether from the contract by claiming rewards multiple times, beyond what they are entitled to.

Tools Used

Manual Review

Recommendations

Apply CEI pattern

function claimSingleReward(uint256 _index) public {
require(rewardsOwned[msg.sender].length > 0, "No rewards to claim"); // Explicit check for rewards
require(_index < rewardsOwned[msg.sender].length, "Invalid index");
uint256 value = rewardsOwned[msg.sender][_index].value;
require(value > 0, "No reward to claim");
// Effects: Update state before interacting with external contract
delete rewardsOwned[msg.sender][_index];
// Interactions: Send Ether after the state has been updated
(bool success,) = payable(msg.sender).call{value: value}("");
require(success, "Transfer failed");
}
Updates

Appeal created

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`claimSingleReward` reentrancy

Support

FAQs

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