Project

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

Reentrancy in createNewDAOMembership

Summary : The createNewDAOMembership function in the MembershipFactory contract is vulnerable to reentrancy attacks. This function creates a new DAO membership and initializes it with the provided configuration.

What is the problem?

The problem is that the createNewDAOMembership function makes an external call to the TransparentUpgradeableProxy contract to initialize the new DAO membership. However, this external call is not properly protected against reentrancy attacks.

/// @notice Creates a new DAO membership
/// @param daoConfig The configuration for the DAO
/// @param tierConfigs The configurations for the tiers
/// @return The address of the newly created Membership ERC1155 proxy contract
function createNewDAOMembership(DAOInputConfig calldata daoConfig, TierConfig[] calldata tierConfigs)
external returns (address) {
require(currencyManager.isCurrencyWhitelisted(daoConfig.currency), "Currency not accepted.");
require(daoConfig.noOfTiers == tierConfigs.length, "Invalid tier input.");
require(daoConfig.noOfTiers > 0 && daoConfig.noOfTiers <= TIER_MAX, "Invalid tier count.");
require(getENSAddress[daoConfig.ensname] == address(0), "DAO already exist.");
if (daoConfig.daoType == DAOType.SPONSORED) {
require(daoConfig.noOfTiers == TIER_MAX, "Invalid tier count for sponsored.");
}
// enforce maxMembers
uint256 totalMembers = 0;
for (uint256 i = 0; i < tierConfigs.length; i++) {
totalMembers += tierConfigs[i].amount;
}
require(totalMembers <= daoConfig.maxMembers, "Sum of tier amounts exceeds maxMembers.");
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
membershipImplementation,
address(proxyAdmin),
abi.encodeWithSignature("initialize(string,string,string,address,address)", daoConfig.ensname, "OWP", baseURI, _msgSender(), daoConfig.currency)
);
DAOConfig storage dao = daos[address(proxy)];
dao.ensname = daoConfig.ensname;
dao.daoType = daoConfig.daoType;
dao.currency = daoConfig.currency;
dao.maxMembers = daoConfig.maxMembers;
dao.noOfTiers = daoConfig.noOfTiers;
for (uint256 i = 0; i < tierConfigs.length; i++) {
require(tierConfigs[i].minted == 0, "Invalid tier config");
dao.tiers.push(tierConfigs[i]);
}
getENSAddress[daoConfig.ensname] = address(proxy);
userCreatedDAOs[_msgSender()][daoConfig.ensname] = address(proxy);
emit MembershipDAONFTCreated(daoConfig.ensname, address(proxy), dao);
return address(proxy);
}

Vulnerability Details: The createNewDAOMembership function in the MembershipFactory contract is vulnerable to reentrancy attacks because it makes an external call to the TransparentUpgradeableProxy contract to initialize the new DAO membership. This external call is not properly protected against reentrancy attacks, which can lead to unintended behavior and potential security vulnerabilities.

What is a Reentrancy Attack?

A reentrancy attack occurs when a contract calls another contract, and the called contract executes a function that makes another call to the original contract, causing the original contract to execute its function again. This can lead to unintended behavior and potential security vulnerabilities.

How Does the createNewDAOMembership Function Make an External Call?

The createNewDAOMembership function makes an external call to the TransparentUpgradeableProxy contract to initialize the new DAO membership. This external call is made using the new keyword, which creates a new instance of the TransparentUpgradeableProxy contract.

What is the Problem with the External Call?

The problem with the external call is that it is not properly protected against reentrancy attacks. When the createNewDAOMembership function makes the external call to the TransparentUpgradeableProxy contract, it does not check whether the called contract has already executed its function. This means that if the called contract has already executed its function, it can reenter the createNewDAOMembership function and cause unintended behavior.

How Can an Attacker Exploit this Vulnerability?

An attacker can exploit this vulnerability by creating a malicious contract that calls the createNewDAOMembership function and then reenters the contract by calling the getENSAddress function. This can cause the contract to execute unintended code and potentially lead to security vulnerabilities.

Example of a Reentrancy Attack

Here is an example of how an attacker can exploit the reentrancy vulnerability:

pragma solidity ^0.8.22;
contract Attacker {
address public membershipFactoryAddress;
constructor(address _membershipFactoryAddress) public {
membershipFactoryAddress = _membershipFactoryAddress;
}
function attack() public {
// Call the createNewDAOMembership function to create a new DAO membership
MembershipFactory(membershipFactoryAddress).createNewDAOMembership(DAOInputConfig("mydao", "0x...", "0x..."), TierConfig[](1, 100, "0x..."));
// Reenter the contract by calling the getENSAddress function
MembershipFactory(membershipFactoryAddress).getENSAddress("mydao");
}
}

In this example, the attacker calls the createNewDAOMembership function to create a new DAO membership and then reenters the contract by calling the getENSAddress function. This can cause the contract to execute unintended code and potentially lead to security vulnerabilities.

Impact: The consequences of this vulnerability are severe and can have a significant impact on the contract and the platform. Here's a more detailed explanation of each of the consequences:

Security Breaches

An attacker can exploit this vulnerability to gain unauthorized access to the contract's functionality. This means that the attacker can execute functions that they should not have access to, potentially allowing them to:

  • Steal funds: The attacker can use the contract's functionality to transfer funds to their own account, resulting in financial loss for the contract's users.

  • Manipulate the contract's state: The attacker can modify the contract's state to their advantage, potentially allowing them to gain an unfair advantage or disrupt the normal functioning of the contract.

Financial Loss

An attacker can exploit this vulnerability to steal funds or manipulate the contract's state, leading to financial loss for the contract's users. This can happen in several ways:

  • Theft of funds: The attacker can use the contract's functionality to transfer funds to their own account, resulting in financial loss for the contract's users.

  • Manipulation of the contract's state: The attacker can modify the contract's state to their advantage, potentially allowing them to gain an unfair advantage or disrupt the normal functioning of the contract.

Reputation Damage

The contract and the platform may suffer reputational damage as a result of this vulnerability. This can happen in several ways:

  • Loss of credibility: If the contract is exploited and users lose funds or experience other negative consequences, the contract and the platform may lose credibility with their users.

  • Loss of trust: If the contract is exploited and users lose trust in the contract and the platform, they may be less likely to use the contract or platform in the future.

  • Negative publicity: The exploitation of the contract may receive negative publicity, which can further damage the reputation of the contract and the platform.

Overall, the consequences of this vulnerability are severe and can have a significant impact on the contract and the platform. It's essential to address this vulnerability as soon as possible to prevent any potential harm to users and to maintain the integrity of the contract and the platform.

Example of Reputation Damage

Let's say that the contract is a popular decentralized finance (DeFi) protocol that allows users to lend and borrow cryptocurrencies. If an attacker exploits the vulnerability and steals funds from the contract, the contract and the platform may suffer reputational damage. Users may lose trust in the contract and the platform, and the negative publicity may deter new users from using the contract or platform.

In this example, the reputation damage can be severe, and it may take a long time for the contract and the platform to recover. The contract and the platform may need to take steps to regain the trust of their users, such as implementing additional security measures or providing compensation to affected users.

Proof of Concept Code : This example assumes that the MembershipFactory contract does not yet include a reentrancy guard.

MembershipFactory Contract (Vulnerable)

First, let’s present a simplified version of the vulnerable MembershipFactory contract for context:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
contract MembershipFactory is AccessControl {
bytes32 public constant EXTERNAL_CALLER = keccak256("EXTERNAL_CALLER");
mapping(address => DAOConfig) public daos;
mapping(string => address) public getENSAddress;
mapping(address => mapping(string => address)) public userCreatedDAOs;
struct TierConfig {
uint256 amount;
uint256 minted;
string configData;
}
struct DAOInputConfig {
string ensname;
string daoType;
address currency;
}
struct DAOConfig {
string ensname;
string daoType;
address currency;
uint256 maxMembers;
uint256 noOfTiers;
TierConfig[] tiers;
}
event MembershipDAONFTCreated(string ensname, address proxy, DAOConfig dao);
address public membershipImplementation;
address public proxyAdmin;
string public baseURI;
constructor(address _membershipImplementation, address _proxyAdmin, string memory _baseURI) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
membershipImplementation = _membershipImplementation;
proxyAdmin = _proxyAdmin;
baseURI = _baseURI;
}
function createNewDAOMembership(DAOInputConfig calldata daoConfig, TierConfig[] calldata tierConfigs)
external returns (address) {
require(getENSAddress[daoConfig.ensname] == address(0), "DAO already exists.");
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
membershipImplementation,
address(proxyAdmin),
abi.encodeWithSignature("initialize(string,string,string,address,address)", daoConfig.ensname, "OWP", baseURI, msg.sender, daoConfig.currency)
);
DAOConfig storage dao = daos[address(proxy)];
dao.ensname = daoConfig.ensname;
dao.daoType = daoConfig.daoType;
dao.currency = daoConfig.currency;
for (uint256 i = 0; i < tierConfigs.length; i++) {
require(tierConfigs[i].minted == 0, "Invalid tier config");
dao.tiers.push(tierConfigs[i]);
}
getENSAddress[daoConfig.ensname] = address(proxy);
userCreatedDAOs[msg.sender][daoConfig.ensname] = address(proxy);
emit MembershipDAONFTCreated(daoConfig.ensname, address(proxy), dao);
return address(proxy);
}
}

Attacker Contract

Here’s a proof of concept for an attacker contract that exploits the reentrancy vulnerability:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import "./MembershipFactory.sol";
contract Attacker {
MembershipFactory public membershipFactory;
bool public attackStarted;
constructor(address _membershipFactoryAddress) {
membershipFactory = MembershipFactory(_membershipFactoryAddress);
}
fallback() external payable {
if (attackStarted) {
attackStarted = false;
membershipFactory.createNewDAOMembership(
DAOInputConfig("maliciousDao", "0x...", address(this)),
new MembershipFactory.TierConfig[](1)
);
}
}
function attack() public {
attackStarted = true;
membershipFactory.createNewDAOMembership(
DAOInputConfig("initialDao", "0x...", address(this)),
new MembershipFactory.TierConfig[](1)
);
}
}

Explanation

  • MembershipFactory Contract: Contains a function createNewDAOMembership that allows creating a new DAO membership. This function has a potential reentrancy vulnerability.

  • Attacker Contract:

    • Constructor: Initializes with the address of the vulnerable MembershipFactory contract.

    • Fallback Function: Triggered whenever the contract receives Ether. If attackStarted is true, it re-enters the createNewDAOMembership function.

    • Attack Function: Sets attackStarted to true and calls createNewDAOMembership on the MembershipFactory contract, starting the reentrancy attack.

Tools Used : VS code

Recommendations :To prevent this reentrancy attack, use OpenZeppelin’s ReentrancyGuard. Here’s how you update the MembershipFactory contract to use it:

  1. **Import and Inherit **ReentrancyGuard:

    solidity

    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
    contract MembershipFactory is AccessControl, ReentrancyGuard {
    ...
  2. Apply nonReentrant Modifier:

    solidity

    function createNewDAOMembership(DAOInputConfig calldata daoConfig, TierConfig[] calldata tierConfigs)
    external nonReentrant returns (address) {
    ...

By adding the nonReentrant modifier, you prevent the function from being re-entered, thus mitigating the reentrancy vulnerability.

Updates

Lead Judging Commences

0xbrivan2 Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
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.