The claimSingleReward function allows users to claim a single reward from their list of owned rewards by specifying the index. However, the current implementation is vulnerable to a reentrancy attack, similar to the vulnerability in the claimAllRewards function. This vulnerability could allow a malicious user to exploit the contract and drain funds by repeatedly calling the function before the state is updated.
The vulnerability is due to the fact that the function transfers funds to the caller before updating the state variable rewardsOwned[msg.sender][_index]. This allows an attacker to exploit the reentrancy flaw and repeatedly withdraw rewards for the same index before the state is updated.
1=> Index Check and Value Retrieval: The function first verifies that the provided index _index is valid:
Then, it retrieves the reward value from the specified index:
If the value is greater than zero, the function proceeds to transfer the funds.
2=> Funds Transfer: The contract uses a low-level call to transfer the reward to the caller:
This is the point where the vulnerability occurs. If msg.sender is a contract, it can execute arbitrary code in its fallback function. The contract can then re-enter the claimSingleReward function, allowing it to claim rewards for the same index multiple times before the state is updated.
3=> State Update Vulnerability: The state update occurs after the transfer:
Because this state update happens after the funds are transferred, an attacker can re-enter the function and claim rewards multiple times for the same index before rewardsOwned[msg.sender][_index] is deleted.
An attacker’s contract calls claimSingleReward() with a valid _index.
The contract transfers the reward to the attacker's contract.
The attacker's contract uses its fallback function to re-enter claimSingleReward() and repeatedly claim the reward for the same index before the state is updated.
The attacker drains the contract’s funds by exploiting this vulnerability.
Deploy me Exploit Contract Using the game contract address and fund it with any amount (5 ether)
Call the buyAndOpenBox function 5 times
Call the findMostValuableReward function to get the most valuable reward you have
Then Call the attack function to activate the `Reentrancy Vulnerability` and drain the game contract.
Fund Drainage: The attacker can continuously claim rewards for the same index, draining the contract’s balance.
Denial of Service: Legitimate users may be unable to claim their rewards as the contract’s funds could be depleted by the attacker.
Remix IDE
gas Used to Avoid Revert While Running The Attack: 80000000
The function should update the state before transferring any funds to prevent reentrancy. This can be done by using the Checks-Effects-Interactions pattern, where the contract’s state is updated before interacting with external accounts (such as making a funds transfer).
The updated function would look like this:
By deleting rewardsOwned[msg.sender][_index] before transferring funds, the attacker cannot re-enter the function and claim the reward multiple times.
Implement Checks-Effects-Interactions Pattern: Ensure that all state-changing operations occur before external calls like fund transfers.
Use OpenZeppelin’s ReentrancyGuard: Apply the nonReentrant modifier to prevent reentrancy by blocking re-entrance into the function.
Gas Limit for .call(): Consider limiting the gas available for external calls to make it harder for the fallback function to perform complex operations during the reentrancy window.
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.