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

Deterministic, bad randomness in `PuppyRaffle::selectWinner` when determining the winner of the raffle

Using deterministic inputs for the random number block.difficulty, block.number and msg.sender creates a pseudo-random number when calculating the winner in PuppyRaffle::selectWinner. The value of block.number can be determined and block.difficulty is always 0 since the migration of proof-of-work (PoW) to proof-of-stake (PoS). By executing a transaction in the same block and modifying msg.sender, the desired winner index can be selected and the winner controlled.

Vulnerability Details

In PuppyRaffle:selectWinner on line 128-130, the winner is determined using the following calculation:

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

The block.timestamp, msg.sender, and block.difficulty have been used as seed values for the random index for the players array. Since using block-related properties lead to pseudo-random numbers (see the "Bad Randomness Is Even Dicier than You Think" Medium article for more information), the winnerIndex and therefore the winner are deterministic and subject to manipulation.

msg.sender can be controlled by the selectWinner() caller and since the migration from PoW to PoS, block.difficulty is always 0, the only value which varies is block.number. Since block.number can be determined by including a transaction in the same block, the required value of msg.sender can be calculated for a given block.number to yield the desired winnerIndex and manipulate the winner of the lottery.

Impact

The impact of bad randomness used to determine the winner of a lottery is that an attacker can control who wins the lottery by either simulating a transaction and waiting until the block.number yields the desired winnerIndex for a given msg.sender address or by calculating the required msg.sender for a given block.number. This means that the lottery is not truly random or fair and is therefore a high-severity vulnerability as the fairness of a lottery is a fundamental capability.

Proof of Concept

There are two ways to manipulate the winnerIndex:

  1. Perform the winnerIndex calculation and wait for the block.number to yield the desired winnerIndex for a constant msg.sender address.

  2. Calculate the msg.sender address required to yield the desired winnerIndex for a given block.number.
    To calculate which address would be needed to yield the desired winnerIndex, an attacker can run a script that generates addresses using CREATE2. When the script finds the required address, that satisfies the conditions of yielding the required winnerIndex for a given block.number, the address can be deployed using the generated salt. This address can then be used to call selectWinner() which will determine the winner to be the address the attacker desires.
    The attacker could modify a scrip e.g. https://github.com/0age/create2crunch but change the stopping condition to be when the desired index is calculated for a given block.number.

Recommended Mitigation

Block-related properties, when used as seed values to create random numbers, can be viewed and the random values exploited. Therefore, they should not be used as seed values for random number generation as they are deterministic and are therefore pseudo-random.

Instead, random numbers should be calculated off-chain where possible, or verifiably random functions e.g. Chainlink VRF, should be used to generate random values. VRF uses an Oracle network to generate random numbers and verify their randomness before posting on-chain. VRF uses open-source code and cryptography to create a tamper-proof source of randomness that users can verify as fair and unbiased. The proof is published and verified on-chain before any consuming application can use it. For more information, visit the Chainlink documentation.

Tools Used

Updates

Lead Judging Commences

Hamiltonite Lead Judge almost 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.