The check in the require()
statement in the first line of the PuppyRaffle:withdrawFees()
function can be manipulated by a malicious contract to always be false. As a result, any fees in the contract can never be withdrawn.
The check address(this).balance == uint256(totalFees)
in the require()
statement in PuppyRaffle:withdrawFees()
relies on the balance of the contract matching the value of the PuppyRaffle:totalFees
storage variable. The value of PuppyRaffle:totalFees
is calculated in PuppyRaffle:selectWinner()
whereas the balance of the contract can be manipulated by a malicious contract forcibly sending ETH to PuppyRaffle
OR by a player entering the raffle and then getting a refund.
Since the comparison is looking for equality, this require()
statement will always return false in this situation, preventing any fees in the contract from ever being withdrawn.
Any accumulated fees stored in the contract can not be withdrawn, and will be locked forever. There is no way for the contract to refuse to accept ETH that is forcibly sent to it.
Easily exploited - see the Foundry/Forge PoCs below. In Code1, the test testLockFeesByForceTransfer()
should be copied into the PuppyRaffleTest
contract, and makes use of the PuppyRaffleTest:playersEntered()
function. The HackForceTransfer
contract is the attack contract.
A simpler PoC is given in Code2 - it doesn't require wasting any ETH (except for gas). The malicious player simply enters the raffle and then requests a refund. The test testLockFeesByGettingRefund()
should be copied into the PuppyRaffleTest
contract, and makes use of the PuppyRaffleTest:playersEntered()
function.
Foundry/Forge
Use some other method of determining if there are still active players.
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.