The withdrawFees function in the PuppyRaffle contract is vulnerable to external manipulations, particularly through the selfdestruct function from malicious contracts. By forcing the balance of the PuppyRaffle contract to change using selfdestruct, an attacker can ensure that the withdrawFees function perpetually reverts, making it impossible to withdraw fees.
In the withdrawFees
function of the PuppyRaffle contract, a require
statement is used to check that the contract's balance matches the totalFees
variable before proceeding with the withdrawal. Specifically, the line in question is:
The vulnerability arises from the dependency on the address(this).balance
to determine the state of the contract. An external actor can manipulate the contract's balance through mechanisms such as the selfdestruct
function, as demonstrated in the provided Attack contract. When an attacker uses selfdestruct
, the contract's balance can be forcibly changed without updating the totalFees
variable, making the above require
statement always fail.
Such an attack can disrupt the normal functioning of the PuppyRaffle system by preventing legitimate fee withdrawals. It can also be used to trap funds or deny services to legitimate users, thereby eroding trust in the system and causing potential financial loss to stakeholders.
The Attack
contract is initialized using the address of PuppyRaffle
.
At the start of the test, the raffle proceeds as usual and a winner is selected.
The Attack
contract then executes the withdrawFeesAttackBySelfdestruct
function, using selfdestruct
to forcibly alter the balance of PuppyRaffle
.
Due to this forced balance change, the withdrawFees
function in PuppyRaffle
fails because the actual balance of the contract no longer matches the totalFees
variable.
The test verifies this failure and catches the expected revert.
Attack
Test
Foundry
Issue: The current implementation uses the contract's balance as a mechanism to determine if fees can be withdrawn. This approach can be prone to errors and may not reliably reflect the actual state of the raffle.
Recommendation: Implement a state management system using Solidity's enum
to make the contract's logic more intuitive and robust.
Issue: When new participants enter the raffle, there is no explicit mechanism that indicates the raffle has resumed and is open for participation.
Recommendation: Update the enterRaffle
function to set the raffle's state to Open
whenever new players are added.
Issue: The contract does not explicitly signal the end of a raffle round once a winner has been chosen.
Recommendation: Modify the selectWinner
function to set the raffle's state to Closed
after a winner is determined, signaling that the raffle round has concluded.
Issue: The current mechanism to determine if fees can be withdrawn is based on checking the contract's balance. This is not a reliable or clear method.
Recommendation: Change the withdrawFees
function to require the raffle state to be Closed
before allowing the withdrawal of fees. This ensures fees can only be withdrawn after a raffle round has ended.
Root cause: bad RNG Impact: manipulate winner
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.