The MysteryBox::claimSingleReward contains an off-by-one error in array indexing, combined with insufficient reentrancy protection. This combination allows an attacker to potentially drain funds from the contract by exploiting the faulty array bounds check and repeatedly calling the vulnerable function before the state is updated.
Off-by-one error: userIndex[msg.sender] is initialized to balances.length, which is one index beyond the array's valid range.
Lack of reentrancy protection: The balance is updated after the transfer, allowing for potential reentrancy attacks.
An attacker can exploit this by:
Calling deposit() to initialize their balance.
Calling withdraw() with a small amount, triggering the vulnerable state.
In the fallback function, repeatedly calling withdraw() before the balance is updated.
Unauthorized withdrawal of funds
Potential complete drainage of contract balance
Loss of user trust and financial damage to the protocol
Manual Review, Foundry
Fix the off-by-one error: The condition require(_index <= rewardsOwned[msg.sender].length, "Invalid index"); allows _index to be equal to the length of the array. However, array indices are zero-based, so the valid indices should be from 0 to rewardsOwned[msg.sender].length - 1.
To fix this, change the condition to
Reentrancy protection: It’s best practice to update state (e.g., delete rewardsOwned[msg.sender][_index];) before transferring Ether to avoid potential reentrancy attacks.
You can reorder the code like this:
By clearing the reward first, you protect the contract from malicious external calls that could exploit the Ether transfer.
I recommend checking all instances in the contract where Ether is transferred and ensuring this pattern is followed.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.