Mystery Box

First Flight #25
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: medium
Valid

H-5 Inconsistent Randomness in `openBox()` function Leads to Repeated Rewards when transacted at the same block

Summary

The openBox() function generates repeated rewards due to the reliance on block.timestamp and msg.sender for randomness.

Vulnerability Details

Block Timestamp Dependency: Transactions occurring within the same block share identical block.timestamp values, reducing the randomness of reward allocation.

User Address Influence: Utilizing msg.sender in the randomness calculation can lead to repeated rewards for users executing multiple transactions in a single block.

This design flaw allows users to exploit the openBox() function, potentially receiving the same reward multiple times when transacting within the same block.

The current code snippet is as follows:

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

Since block.timestamp only updates with each new block and msg.sender remains constant for the same user, transactions that happen within the same block can produce identical random values. This can lead to users receiving the same reward multiple times.

Impact

The predictable nature of the random number generation results in users receiving the same rewards if they interact with the contract multiple times within the same block. This undermines the intended randomness of the reward distribution and could allow certain malicious users to manipulate the system to their favor.
Economic Impact: Repeated rewards of high tier rewards can distort the economic model of the application, affecting sustainability.

Tools Used

Unit testing with Forge

POC

Add this code to your test suite:

function testPlayerCanClaim() public {
//
vm.startPrank(user1);
mysteryBox.buyBox{value: 0.1 ether}();
uint r = mysteryBox.openBox();
console2.log("REWARD:", r);
mysteryBox.claimSingleReward(0);
//
vm.startPrank(user1);
mysteryBox.buyBox{value: 0.1 ether}();
uint a = mysteryBox.openBox();
console2.log("REWARD:", a);
mysteryBox.claimSingleReward(1);
//
vm.startPrank(user2);
mysteryBox.buyBox{value: 0.1 ether}();
uint b = mysteryBox.openBox();
console2.log("REWARD:", b);
mysteryBox.claimSingleReward(0);
//
vm.startPrank(user2);
mysteryBox.buyBox{value: 0.1 ether}();
uint c = mysteryBox.openBox();
console2.log("REWARD:", c);
mysteryBox.claimSingleReward(1);
//
vm.startPrank(user2);
mysteryBox.buyBox{value: 0.1 ether}();
uint q = mysteryBox.openBox();
console2.log("REWARD:", q);
mysteryBox.claimSingleReward(2);
//
vm.startPrank(user3);
mysteryBox.buyBox{value: 0.1 ether}();
uint w = mysteryBox.openBox();
console2.log("REWARD:", w);
mysteryBox.claimSingleReward(0);
//
vm.startPrank(user3);
mysteryBox.buyBox{value: 0.1 ether}();
uint t = mysteryBox.openBox();
console2.log("REWARD:", t);
mysteryBox.claimSingleReward(1);
//
vm.startPrank(user4);
mysteryBox.buyBox{value: 0.1 ether}();
uint y = mysteryBox.openBox();
console2.log("REWARD:", y);
mysteryBox.claimSingleReward(0);
//
vm.startPrank(user4);
mysteryBox.buyBox{value: 0.1 ether}();
uint f = mysteryBox.openBox();
console2.log("REWARD:", f);
mysteryBox.claimSingleReward(1);
assertEq(mysteryBox.getBalance(), 0.8 ether);
assertEq(user3.balance, 1 ether);
}

Recommendations

Secure Randomness Source: Implement a verifiable randomness source like Chainlink VRF to ensure reliable randomness.

Incorporate Additional Variables: Use other variables (e.g., nonce) in the randomness calculation to ensure diverse rewards.

Transaction Frequency Limitation: Limit the number of times a user can call openBox() within a block to prevent exploitation.

Implement a Delay Mechanism: Introduce a cooldown period to restrict rapid successive calls to the openBox() function.

Updates

Appeal created

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Weak Randomness

Support

FAQs

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

Give us feedback!