In the GivingThanks
contract, the donate
function calls an external contract (the charity) via a low-level call with the call{value: msg.value}("")
statement. This introduces a potential security vulnerability because the outcome of the external call is not fully checked, and the contract doesn’t handle the possibility of unexpected behavior from the external contract.
The vulnerability is located in the donate
function of the GivingThanks
contract, specifically at Line 10:
This line sends Ether to an external address and checks if the call was successful using the sent
variable. However, the sent
variable is not used beyond this point, and there is no subsequent logic to handle failure cases properly.
Silent Failure of External Calls:
The call
function returns a boolean indicating success or failure, but this value is not used to handle failure cases. If the external contract (charity) fails to accept the Ether, the funds are still deducted from the sender's account, but the transaction may fail silently without any feedback. This could cause funds to be lost without the user being aware of the failure.
Lack of Revert on Failure:
After the call to the charity contract, the contract doesn't revert or properly handle cases when the sent
value is false
. If the call to the charity address fails (e.g., the charity contract doesn't have a payable function or is not capable of receiving Ether), the transaction continues without reverting or notifying the user of the failure.
Potential Reentrancy Attack Vector:
The contract calls an external address (charity) before updating the state (minting the NFT and setting the token URI). While there is no immediate reentrancy concern in this specific implementation because the contract only accepts Ether and doesn't involve token transfers that could trigger other contracts, the use of call
can introduce a reentrancy risk when the external contract (charity) implements fallback functions or reverts in certain conditions. The external call to an untrusted contract is generally considered a risky practice and should be handled cautiously.
Loss of Funds: If the charity contract does not have a proper payable
function, or if the contract is malicious and intentionally fails to accept Ether, the funds will be sent and lost without the user being notified. For example, if a charity contract does not implement receive()
or fallback()
, the Ether sent will be rejected, but the donation is still processed without an appropriate error message.
Unintended Donation Failure: Users who donate to charities with contracts that do not properly handle incoming funds will lose their donations without proper notification. This may lead to confusion and a negative user experience.
Reentrancy Attack: If the charity contract has a fallback()
function that calls back into the GivingThanks
contract (or another malicious contract), there could be unintended behavior, although this is less likely given the context. Still, the general practice of using call
introduces potential attack surfaces.
Funds Loss: Users could lose their Ether without realizing that the charity contract did not accept the funds.
Unclear User Experience: Donors may donate and receive an NFT receipt but may never know if their donation actually went through, causing dissatisfaction and confusion.
Potential Exploits: If the charity contract is malicious or improperly designed, attackers may exploit the external call to trigger unexpected behavior.
The call
function is used to send Ether to the charity address, but the outcome of the call is not properly handled. Although the sent
value indicates success or failure, it is not checked, and the function proceeds without any corrective action or notification in case of failure. This makes the function prone to failure without feedback.
Funds Loss: Donors might lose Ether if the external contract (charity) fails to accept it, without receiving any feedback or error message.
User Confusion: If the transaction fails silently, users may not know whether their donation went through or not.
Potential Exploit: While reentrancy is not an immediate concern, using call
with external contracts without proper checks always carries some level of risk. Malicious contracts could potentially exploit this vulnerability.
Slither: Used to detect and analyze unhandled external calls and potential attack vectors.
MyEtherWallet: Used to simulate and test the behavior of the donation function with different charity addresses (valid and invalid).
Foundry: For writing test cases and performing simulations to detect the vulnerability in action.
To fix this vulnerability, consider the following changes:
call
Return Value:Ensure that the return value of the call
function is properly checked, and revert the transaction if the call fails. This will prevent users from unknowingly losing funds.
This simple change ensures that the transaction reverts if the charity cannot receive the donation, which protects users from losing funds.
transfer
or send
for Safer Ether Transfers:Instead of using the low-level call
function, consider using transfer
or send
for transferring Ether to well-defined contracts. These methods automatically revert on failure, providing a more predictable and secure behavior. However, note that transfer
has a gas limit (2300 gas), so it may not be suitable for contracts with complex logic in their fallback functions.
receive()
or fallback()
Functions:Before sending funds to the charity, you could ensure that the charity address is capable of receiving Ether by checking if it has a receive()
or fallback()
function. This would help prevent donations to contracts that can't accept Ether.
To improve user experience, add proper event logging for donations, and include detailed error messages when the transaction fails. This will give users a clearer understanding of whether their donation succeeded or failed.
This vulnerability occurs because the external call to the charity address is not properly validated, which could cause donations to fail silently without feedback to the user.
Attacker: An attacker could exploit this vulnerability by providing a charity address that does not accept Ether or that has malicious logic in its fallback function.
Victim: The victim is the donor who unknowingly loses funds if the charity contract fails to accept the donation.
Protocol: The GivingThanks
contract.
The unchecked external call vulnerability in the donate
function is a significant issue, as it could result in a loss of funds for users if the charity address is invalid or the charity contract cannot accept Ether. To mitigate this, the return value of the call
should be checked and handled properly, and the contract should ensure that the charity address is capable of receiving donations.
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.