This is my very first finding and submission in the smart contract space. Excuse me, if the report is not that well written and might be a bit confusing. This First Flight was a lot of fun and a ton of new stuff to learn for me, a lot of thanks to you guys!
The refund(uint256) function does not correctly remove a players index from the array, but sets it to the 0-address. This leads to the <array>.size() not being decremented. Because <array>.size is used in further calculations, this leads to a malicious actor being able to drain the contracts fees.
The refund(uint256) functions comments (@dev This function will allow there to be blank spots in the array) reveal, that there are blank spots in the array. I wrote a test to see, if that affects the <array>.size value:
As expected, the <array>.size() is not decremented. While running the test above with forge test -f $LOCAL_RPC_URL --mt testWallePlayerArrayLengthReducedAfterRefund -vvvv, specific lines of output caught my eye:
The OutOfFund error occurs, when a contracts balance is smaller than the value it tries to send. After further examining the selectWinner() function, these three lines stand out:
Apparently, the <array>.length (=players.length) value is used to calculate the amount of entrance fees payed by the players as well as the final prize pool. Since players, that refunded, are not correctly removed from the list of players, they still count towards the final prize pool. The refund(uint256) function not only does not remove the players correctly, it fails to deduct their fee payback from the final prize pool as well.
A malicious actor can use this to continously add and refund new players to the raffle to increase the prize pool (up to a maximum of the contracts balance, which was 0 in my previous test, which is why it reverted).
This can further be combined with another vulnerability, which I will describe in another report, that allows the winner of a raffle to be predicted (and even changed) prior to the end of a raffle due to insufficient randomness.
Let's summarize the attack:
<array>.size() in the refund(uint256) function does not correctly remove players from the array
New players can arbitrarily be added and refunded to the raffle, inflating the array size
the raffle's prize pool uses this size to calculate the final prize pool (which is then higher as intended)
the winner of the raffle can be predicted (and altered) to match the attackers address
the contract pays out the raffle's prize pool + an amount depending on the number of players joining and refunding during the raffle => loss of fees collected
See the following test code to test for the vulnerability:
Run the test at least with -vv to see the logs: forge test -f $LOCAL_RPC_URL --mt testWallePlayerCantDrainFees -vv
Exploiting this vulnerability may lead to complete loss of funds. This highly depends on how many players can join and immediately refund without changing the winner to someone else than the attacker and not exceeding the contract.balance.
foundry
remove refunding players from the player array or
remove the entrance fee payed back to refunding players from the prize pool
Funds are locked to no one. If someone gets the refund issue, they also got this issue. IMPACT: High Likelihood: High
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.