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

`PuppyRaffle::selectWinner` can be exploited an attacker to mint rare & legendary NFTs

Summary

The rarity random number generation of the PuppyRaffle::selectWinner function can be gamed to mint the rare and legendary NFTs with a predictability attack.

Vulnerability Details

function selectWinner() external {
require(block.timestamp >= raffleStartTime + raffleDuration, "PuppyRaffle: Raffle not over");
require(players.length >= 4, "PuppyRaffle: Need at least 4 players");
@> uint256 winnerIndex =
uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length; // previously covered vulnerable line of code
address winner = players[winnerIndex];
uint256 totalAmountCollected = players.length * entranceFee;
uint256 prizePool = (totalAmountCollected * 80) / 100;
uint256 fee = (totalAmountCollected * 20) / 100;
totalFees = totalFees + uint64(fee);
uint256 tokenId = totalSupply();
// We use a different RNG calculate from the winnerIndex to determine rarity
@> uint256 rarity = uint256(keccak256(abi.encodePacked(msg.sender, block.difficulty))) % 100; // vulnerable line of code
if (rarity <= COMMON_RARITY) {
tokenIdToRarity[tokenId] = COMMON_RARITY;
} else if (rarity <= COMMON_RARITY + RARE_RARITY) {
tokenIdToRarity[tokenId] = RARE_RARITY;
} else {
tokenIdToRarity[tokenId] = LEGENDARY_RARITY;
}
delete players;
raffleStartTime = block.timestamp;
previousWinner = winner;
(bool success,) = winner.call{value: prizePool}("");
require(success, "PuppyRaffle: Failed to send prize pool to winner");
_safeMint(winner, tokenId);
}

Impact

During the selectWinner unprotected function call, I can exploit the contract by minting the not-so-common NFTs. To do this, say I want to mint the st. bernard NFT, I call the function only when the rarity equals 95 to mint the st. bernard NFT. As a follow-up to my previous report about exploiting the function to make myself the winner, I could now create a 2-attack vector which allows me to select myself as a winner and also mint a rare or legendary NFT whenever I am the winner.

This is possible because I just need to call the selectWinner function when the randomness generated in my attack contract matches what the selectWinner randomness will be looking to get.

Tools Used

Manual review

Recommendations

Use the Chainlink VRF function to generate proveably fair numbers.

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!