The refund function in the PuppyRaffle contract does not have any preventive measures against reentrancy attacks. As a result, an attacker can potentially drain all the funds from the contract using a malicious external contract.
In the provided PuppyRaffle contract, the refund function sends ether before marking the player as refunded:
This order of operations allows a malicious contract (like Attack) to re-enter the refund function before the state is updated, draining the contract's balance.
The Attack contract demonstrates this vulnerability. Once a player (the Attack contract in this case) has entered the raffle, it calls the refund function. When sendValue sends ether to the Attack contract, it triggers the receive function, which checks if the PuppyRaffle contract has more than 1 ether. If it does, the attack continues, and the refund function is called again.
An attacker can drain all the ether from the PuppyRaffle contract.
Create an Attack Contract.
This contract has fallback payable function which will call the refund function of PuppyRaffle when it’s called.
Inside the Test contract, I gave 3 ether to the PuppyRaffle contract
Execute the Attack Contract
PuppyRaffle balance is now 0 ether.
Attack.sol
PuppyRaffleTest.t.sol
Foundry
Use the reentrancyGuard modifier from OpenZeppelin to protect against reentrancy attacks. Implement it as follows:
By using nonReentrant, the function will revert if there's a recursive call, preventing the reentrancy attack.
reentrancy in refund() function
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.