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

Unreliable source of randomness allows the attacker to manipulate the raffle results in their favor

Summary

The PuppyRaffle contract uses block.timestamp and block.difficulty as a source of randomness. This is a known anti-pattern, as those variables can be read from the chain by the attacker's smart contract. This allows the attacker to manipulate the PuppyRaffle results to make himself a winner, and to ensure highest rarity of minted NFT.

Vulnerability Details

The winner of the PuppyRaffle gets selected as follows:

uint256 winnerIndex = uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;
address winner = players[winnerIndex];

The variables block.timestamp and block.difficulty can be read by the attacker's smart contract before calling the selectWinner() method. To ensure that the winnerIndex will be their own index, the attacker can either:

  1. Use a specific address while calling the method. The attacker may have deployed their malicous smart contract to different addresses. Before calling the selectWinner() method, the attacker computes the winnerIndex using his different addresses, and calls the selectWinner() with the one that ensures favorable computation result (making himself a winner).

  2. Enter the raffle with certain number of addresses to manipulate the players.length. Knowing the msg.sender, block.timestamp and block.diffuculty beforehand, the attacker may precede the selectWinner() call with certain amount of enterRaffle() calls, ensuring that the winnerIndex calculation will result in the index of one of his own addresses.

Additionaly, the randomness used for token rarity selection is similarly insecure:

uint256 rarity = uint256(keccak256(abi.encodePacked(msg.sender, block.difficulty))) % 100;
if (rarity <= COMMON_RARITY) {
tokenIdToRarity[tokenId] = COMMON_RARITY;
} else if (rarity <= COMMON_RARITY + RARE_RARITY) {
tokenIdToRarity[tokenId] = RARE_RARITY;
} else {
tokenIdToRarity[tokenId] = LEGENDARY_RARITY;
}

In order to win the most rare token, the attacker has to ensure that the result of the rarity calculation will be greater than COMMON_RARITY + RARE_RARITY (95). Once again the attacker may pick a specific address from which he will call the selectWinner() method to ensure the favorable result.

Impact

The raffle results may be manipulated in the attacker's favor.

Tools Used

Manual review

Recommendations

Use a secure source of randomness, such as Chainlink's VRF.

Updates

Lead Judging Commences

Hamiltonite Lead Judge over 1 year 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.