The GivingThanks::donate function is vulnerable to a reentrancy attack due to improper ordering of state changes and external calls. An attacker can exploit this vulnerability to re-enter the donate function multiple times in a single transaction, minting multiple NFTs without making additional donations. This attack remains possible even after correcting the isVerified function, as the donate function does not follow the Checks-Effects-Interactions (CEI) pattern.
Vulnerable Code:
Issue:
The external call to the charity address is made before updating critical state variables like tokenCounter.
An attacker-controlled charity contract can implement a fallback function (receive()) that re-enters the donate function.
Since state changes occur after the external call, the attacker can repeatedly re-enter donate, minting multiple NFTs.
Proof of Concept:
Note: Before running the test, ensure that the constructor in the GivingThanks contract is corrected to properly initialize the registry variable. Update the constructor on line 16 as follows:
The following test demonstrates the reentrancy attack:
Explanation:
The ReentrantContract is registered and verified as a charity.
When donate is called, the Ether is sent to ReentrantContract, triggering the receive() function.
The receive() function re-enters donate, allowing the attacker to mint multiple NFTs without additional donations.
The test confirms that 11 NFTs are minted instead of 1.
Note: This attack works even if the isVerified function is correctly implemented, as the vulnerability lies in the ordering of operations within the donate function.
Unauthorized Minting of NFTs: Attackers can mint multiple NFTs without making corresponding donations.
Financial Loss: Potential loss of funds if the contract holds Ether or if NFTs have monetary value.
State Corruption: Reentrancy can lead to inconsistent state, affecting the contract's integrity.
Reputation Damage: Exploitation undermines user trust and platform credibility.
Forge (Foundry): For writing and running the test cases.
Manual Code Analysis: To identify the improper order of operations.
Solidity Documentation: For understanding the behavior of external calls and reentrancy.
Apply the Checks-Effects-Interactions Pattern:
Rearrange the donate function to update state variables before making external calls:
Use Reentrancy Guards:
Import OpenZeppelin's ReentrancyGuard and apply the nonReentrant modifier to the donate function:
Avoid Calling Untrusted Contracts:
Consider whether it's necessary to send Ether directly to the charity address or if an alternative mechanism can be used.
Implement Access Control:
Ensure that only legitimate interactions are permitted by enforcing strict access controls and validations.
By applying these recommendations, the contract will prevent reentrancy attacks, ensuring that state changes occur before any external interactions, thus maintaining the contract's integrity and security.
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.