Mystery Box

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

The function `MysteryBox::claimSingleReward` does not follow Check-Effects-Interaction (CEI) pattern, leading to a potential reentrancy attack.

Description:

The MysteryBox::claimSingleReward function is intended to allow users to claim a reward associated with their account. However, this function is vulnerable to a reentrancy attack due to the improper sequence of operations in the function body.

The external call to payable(msg.sender).call{value: value}(""); occurs before the state variable rewardsOwned[msg.sender][_index] is updated (via delete). This allows an attacker to re-enter the function before the state change is made, potentially claiming rewards multiple times before the state is updated, resulting in unexpected behavior or loss of funds.

Impact:

If exploited, an attacker could repeatedly call the claimSingleReward function by re-entering it through the call to msg.sender. This would allow the attacker to drain all rewards for a specific account or even other users’ rewards.

Proof of Concept:

Consider the following scenario:

A malicious actor calls claimSingleReward.
Before the delete rewardsOwned[msg.sender][_index] statement is executed, the actor is able to re-enter the function because the state is not yet updated.
The actor calls the function again during the reentrancy, exploiting the fact that the rewards for that index have not been deleted yet.
The actor repeats this process, draining all the rewards.

Tools Used

Manual Review

Recommended Mitigation:

To prevent this reentrancy issue, the function should follow the Check-Effects-Interaction (CEI) pattern by updating the state before making the external call.

function claimSingleReward(uint256 _index) public {
require(_index < rewardsOwned[msg.sender].length, "Invalid index"); // corrected index check
uint256 value = rewardsOwned[msg.sender][_index].value;
require(value > 0, "No reward to claim");
// Update state first to prevent reentrancy
+ delete rewardsOwned[msg.sender][_index];
// Then make the external call
(bool success,) = payable(msg.sender).call{value: value}("");
require(success, "Transfer failed");
- delete rewardsOwned[msg.sender][_index];
}
Updates

Appeal created

inallhonesty Lead Judge 11 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.