Project

One World
NFTDeFi
15,000 USDC
View results
Submission Details
Severity: high
Invalid

The unchecked external call vulnerability exists in the callExternalContract function of the MembershipFactory contract

Summary

The unchecked external call vulnerability exists in the callExternalContract function of the MembershipFactory contract. The issue arises from the use of contractAddress.call{value: msg.value}(data) to perform external calls without properly handling the possibility of reentrancy attacks or malicious contract responses. While the function includes a check for the success of the call (require(success, "External call failed")), it does not address the broader risks associated with untrusted external calls.

Vulnerability Details

In the function callExternalContract, an external contract is called with a specified contractAddress and data. This external call is done via the low-level .call() function, which is prone to reentrancy vulnerabilities, especially when interacting with contracts that can alter the flow of control back to the calling contract before completing execution.

function callExternalContract(address contractAddress, bytes memory data) external payable onlyRole(EXTERNAL_CALLER) returns (bytes memory) {
(bool success, bytes memory returndata) = contractAddress.call{value: msg.value}(data);
require(success, "External call failed");
return returndata;
}

The call function allows for arbitrary code execution, and if the external contract is malicious or improperly written, it can reenter the MembershipFactory contract during the execution of the external call, manipulating state variables or performing unexpected actions, such as draining funds or executing unexpected actions in the caller contract.

Impact

The impact of this vulnerability can be severe:

  1. Reentrancy Attacks: If the called contract is malicious and is able to call back into the MembershipFactory contract before the first call completes, the attacker could manipulate the state of the contract or drain funds (for instance, if the external call involves any ether or token transfer).

  2. Denial of Service (DoS): Malicious external contracts could also cause the callExternalContract function to fail or freeze operations by making the call non-terminating or always fail. This could result in service interruptions, preventing legitimate users from interacting with the contract.

  3. Untrusted Call Risk: Since the contract does not verify or sanitize the behavior of the external contract, an attacker could call a contract that performs harmful actions within the same transaction.

Tools Used

  1. Static Analysis: Manual inspection of the contract code for potential vulnerabilities.

  2. Solidity Compiler Warnings: Review of compiler output to identify any common pitfalls like unhandled exceptions.

  3. Etherscan & MythX: Tools like MythX for deeper analysis of known vulnerabilities in smart contract patterns.

  4. Slither: Automated analysis tool for static analysis, used to identify potential reentrancy and untrusted external call vulnerabilities.

Recommendations

To mitigate this vulnerability, follow these steps:

  1. Use a Reentrancy Guard: Implement a reentrancy guard pattern to ensure that no external calls are made when the contract state is being modified. This can be done by using ReentrancyGuard from OpenZeppelin or creating a custom modifier.

  2. Avoid Low-Level Calls: Instead of using low-level .call() to interact with external contracts, use higher-level functions like IERC20.transfer() or IERC20.transferFrom() where applicable, as they do not expose the contract to the same reentrancy risks.

  3. Check the Returned Data: Ensure that any external contract call has its return data validated, especially when interacting with token transfers or contract state changes.

  4. Limit the Scope of External Calls: Use tighter access control on callExternalContract to limit which addresses can invoke it. For instance, restricting this function to a whitelist of trusted contracts could minimize the impact of an attack.

  5. Gas Stipend Safety: If a call to an external contract involves Ether transfer (via msg.value), ensure that no sensitive contract state can be modified as a result of the transfer. Consider using the transfer function instead of call{value} to limit the gas stipend.

Proof of Concept for Unchecked External Call Vulnerability

Overview:

An attacker could exploit the unchecked external call to trigger a reentrancy attack or manipulate the contract state.

Actors:

  • Attacker: A malicious contract that exploits the vulnerability by calling back into the MembershipFactory contract.

  • Victim: The MembershipFactory contract, which is vulnerable to unexpected behavior due to unchecked external calls.

  • Protocol: The Membership DAO protocol, which is intended to manage memberships and DAOs securely.

Working Test Case (PoC):

  1. A malicious attacker deploys a contract that calls callExternalContract and re-enters the MembershipFactory contract, causing unexpected state changes or fund draining.

// Malicious contract that exploits the reentrancy vulnerability
contract MaliciousContract {
MembershipFactory victim;
// Constructor to set the target victim contract
constructor(address victimAddress) {
victim = MembershipFactory(victimAddress);
}
// Fallback function to call back into the victim contract and exploit reentrancy
fallback() external payable {
// Re-enter the victim contract
victim.callExternalContract(address(this), abi.encodeWithSignature("someFunction()"));
}
// Function to initiate the attack
function attack() external {
victim.callExternalContract(address(this), abi.encodeWithSignature("someFunction()"));
}
}

Exploit Process:

  1. The attacker deploys the MaliciousContract with the address of the vulnerable MembershipFactory contract.

  2. The attacker calls attack() on the malicious contract.

  3. The callExternalContract function in the MembershipFactory contract invokes the malicious contract.

  4. The malicious contract's fallback function is triggered, and it calls the MembershipFactory contract again, potentially manipulating its state or draining funds.

Example Code of the Affected Function:

function callExternalContract(address contractAddress, bytes memory data) external payable onlyRole(EXTERNAL_CALLER) returns (bytes memory) {
(bool success, bytes memory returndata) = contractAddress.call{value: msg.value}(data); // Vulnerable to reentrancy
require(success, "External call failed");
return returndata;
}

Example of How It Was Infected:

  1. The malicious attacker uses the callExternalContract to invoke the fallback function in their contract.

  2. During the execution of the fallback function, the attacker can invoke another function of the vulnerable contract, thus exploiting the reentrancy flaw.

Example of How to Repair:

  1. Use transfer instead of call{value: msg.value} for transferring funds to minimize gas allowance issues and avoid reentrancy.

  2. Add a Reentrancy Guard:

    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
    contract MembershipFactory is AccessControl, NativeMetaTransaction, ReentrancyGuard {
    // Ensure that external calls are protected by the reentrancy guard
    function callExternalContract(address contractAddress, bytes memory data) external payable onlyRole(EXTERNAL_CALLER) nonReentrant returns (bytes memory) {
    (bool success, bytes memory returndata) = contractAddress.call{value: msg.value}(data);
    require(success, "External call failed");
    return returndata;
    }
    }
  3. Verify External Calls: Ensure that external calls are made to trusted contracts and include checks on returned data for additional safety.


Risk Assessment:

  • Risk Type: High

    • The vulnerability can lead to reentrancy attacks, allowing an attacker to manipulate contract state or drain funds, resulting in severe financial loss or protocol compromise.

Updates

Lead Judging Commences

0xbrivan2 Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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