The RockPaperScissors::_finishGame
function is vulnerable to a reentrancy attack. This vulnerability is triggered when external calls to transfer ETH are made before modifying the contract's state, which allows malicious contracts to repeatedly call the function, extracting funds until they are exhausted or the transaction fails.
The contract first performs an ETH transfer to the winner using (bool success, ) = _winner.call{value: prize}("");
But there is no state change for the game.bet or prize before or after this transfer.
The malicious contract can implement a fallback function or receive()
function that gets triggered when it receives ETH.
The attacker reenters the function during the ETH transfer to initiate another transfer, repeating the cycle.
This process allows the attacker to repeatedly call the transfer, draining funds from the contract.
Even if the game.bet parameter is set to 0 before sending the funds, when the malicious contract reenters, they will be able to reach the following piece of code:
Thus, even that will result in the attacker winning 2 tokens.
Another issue that arises due to the attacker reentering the function is that the accumulatedFees
gets incremented every time the function is called. Thus along with the attacker, the admin will also benefit from this vulnerability.
Add the following helper functions in the RockPaperScissors.sol contract:
Then add this Malicious contract in the RockPaperScissorsrTest.t.sol file:
Add this testing function in the RockPaperScissorsTest.t.sol file:
This shoud display the OutOfFunds error thus proving the vulnerability.
The attacker can drain all the ETH from the contract thus causing huge losses.
Manual Review
Use the nonReentrant
keyword to prevent the reentrancy attacks.
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.