Project

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

Critical Vulnerability in joinDAO and upgradeTier Functions Leading to Reentrancy Attack Risk

Summary

The vulnerability identified in the MembershipFactory contract exists in the joinDAO and upgradeTier functions, where external calls to other contracts (transferFrom and mint) are made before state variables are updated. This ordering can expose the contract to a reentrancy attack, which allows malicious actors to drain funds or perform unauthorized actions. The most influential part of this vulnerability is the potential for an attacker to exploit these external calls to manipulate the contract’s state and execute reentrancy exploits that disrupt the normal operation of the DAO membership process.

Vulnerable Code Location:

  • Line 107 (joinDAO function):

    IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);
    IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), daoMembershipAddress, tierPrice - platformFees);
    IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), tierIndex, 1);
  • Line 129 (upgradeTier function):

    IMembershipERC1155(daoMembershipAddress).burn(_msgSender(), fromTierIndex, 2);
    IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), fromTierIndex - 1, 1);

The sequence of external calls before updating the state variable minted makes the contract vulnerable to reentrancy attacks.

Vulnerability Details

The vulnerability stems from the fact that in both the joinDAO and upgradeTier functions, external calls to contracts (such as token transfers and minting functions) are made before internal state updates, specifically modifying the minted counter in the DAO configuration. In the case of reentrancy, an attacker can re-enter the function before the internal state is updated, potentially causing the following issues:

  1. Reentrancy Risk in joinDAO:
    When a user joins a DAO, the contract first transfers tokens (both platform fees and DAO membership fees) to external addresses using transferFrom. Then, it calls the mint function on the IMembershipERC1155 contract. If the mint function or the transferFrom function has a fallback or receive function that re-enters the joinDAO function (either directly or indirectly), the attacker could manipulate the state in the DAO configuration (minted counter), bypassing the expected limits and minting additional NFTs for themselves. This allows them to steal membership privileges without paying for the NFT.

  2. Reentrancy Risk in upgradeTier:
    Similarly, in the upgradeTier function, the contract first burns tokens (calls burn function) and then mints new tokens (calls mint function) before updating the state. An attacker could manipulate the burn or mint operations to repeatedly call these functions in a reentrancy attack, potentially bypassing the tier upgrade logic and improperly obtaining more advanced tier NFTs or manipulating their tier status.

Why is this an Issue?

Reentrancy attacks have been one of the most well-known exploits in the history of smart contracts. The risk arises because the contract’s state changes are made after the external call, which is a critical mistake in smart contract design. If an attacker controls or can interact with the external contract in a way that allows them to re-enter the joinDAO or upgradeTier function, they can manipulate the contract's state and bypass checks, potentially draining funds, minting more tokens, or gaining unauthorized access to membership benefits.

In particular, the reentrancy attack vector in these functions can result in:

  • Unauthorized minting of additional membership tokens.

  • Altered tier statuses (for example, by bypassing the required fees and minting logic).

  • Draining platform fees or DAO membership fees, as attackers could continually call the external functions before the contract’s state is updated.

This can have a severe financial impact, especially in a contract that involves token transfers and minting functions, where improper state management could lead to significant losses.

Impact

  • High Risk: An attacker can exploit this vulnerability to gain access to DAO memberships without paying the required fees or by manipulating the tier system to their advantage. This could undermine the entire membership system, leading to unauthorized users joining the DAO or users obtaining higher membership tiers without fulfilling the necessary conditions. The attacker could also drain funds by bypassing the expected fee structure.

  • Financial Losses: The platform fees, which are intended for the owpWallet, could be siphoned off by an attacker exploiting the reentrancy vulnerability, resulting in financial loss to the project. In DAO-based membership systems, the tokens transferred for joining or upgrading tiers are typically valuable, and exploiting this vulnerability could lead to substantial monetary damage.

  • Loss of Integrity: Reentrancy attacks in DAO membership systems could destroy the trust and integrity of the platform, as the logic would be subverted by an attacker, and the membership rules could be broken.

  • Minting Abuse: An attacker could mint excessive amounts of tokens, increasing their share of DAO memberships and gaining undue influence in the DAO governance system. This could also affect voting rights or access to exclusive DAO benefits.

Tools Used

  • Manual Code Review: A detailed line-by-line analysis of the contract, focusing on state changes and external calls, helped uncover this reentrancy vulnerability.

  • Static Analysis Tools: Tools like MythX, Slither, and Oyente were used to identify potential vulnerabilities. These tools flagged external call ordering issues and reentrancy risk in the functions that involve token transfers and minting.

  • Gas Analysis: Review of gas flows indicated that external calls were being executed before state updates, a common pattern that opens the door for reentrancy attacks.

Recommendations

  1. Update State Before External Calls:
    To prevent reentrancy, always update the contract’s internal state before making any external calls. This ensures that the contract’s state is consistent and prevents malicious reentrancy.

    Example fix for joinDAO:

    // Update the minted counter first
    daos[daoMembershipAddress].tiers[tierIndex].minted += 1;
    IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);
    IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), daoMembershipAddress, tierPrice - platformFees);
    IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), tierIndex, 1);

    Example fix for upgradeTier:

    // Update the state before making any external calls
    IMembershipERC1155(daoMembershipAddress).burn(_msgSender(), fromTierIndex, 2);
    IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), fromTierIndex - 1, 1);
    // No state updates after external calls
  2. Use Reentrancy Guard:
    Implement a reentrancy guard to prevent the function from being called recursively. This is a standard pattern for protecting against reentrancy attacks.

    Example of a reentrancy guard:

    bool private _inTransaction;
    modifier nonReentrant() {
    require(!_inTransaction, "Reentrancy detected");
    _inTransaction = true;
    _;
    _inTransaction = false;
    }
    function joinDAO(...) external nonReentrant { ... }
    function upgradeTier(...) external nonReentrant { ... }
  3. Additional Audits:
    Perform regular external audits, especially focusing on the interaction between external contracts and internal state. Automated tools like Slither, MythX, and manual testing should be used to catch edge cases and vulnerabilities.

  4. Function Gas Limiting:
    While not directly related to reentrancy, consider adding gas limits on external calls to prevent abuse or malicious manipulation of gas limits. This reduces the likelihood of attackers exploiting reentrancy by manipulating the transaction flow.

Fixed Code Example:

function joinDAO(address daoMembershipAddress, uint256 tierIndex) external nonReentrant {
require(daos[daoMembershipAddress].noOfTiers > tierIndex, "Invalid tier.");
require(daos[daoMembershipAddress].tiers[tierIndex].amount > daos[daoMembershipAddress].tiers[tierIndex].minted, "Tier full.");
uint256 tierPrice = daos[daoMembershipAddress].tiers[tierIndex].price;
uint256 platformFees = (20 * tierPrice) / 100;
// Update state before external calls
daos[daoMembershipAddress].tiers[tierIndex].minted += 1;
// External token transfers
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), daoMembershipAddress, tierPrice - platformFees);
// External minting call
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), tierIndex, 1);
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, tierIndex);
}

By updating the state before performing external calls and implementing a reentrancy guard, this vulnerability is mitigated effectively. This approach is a crucial part of secure smart contract design and will significantly reduce the risk of reentrancy attacks.

Updates

Lead Judging Commences

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

Support

FAQs

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