Project

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

Reentrancy in joinDAO function

Summary: The joinDAO function in the MembershipFactory contract is vulnerable to reentrancy attacks. This function allows a user to join a DAO by paying a certain amount of tokens. However, the function makes three external calls to transfer tokens and mint a new membership token, which can be exploited by an attacker to reenter the contract and manipulate its state.

The problem is that the joinDAO function makes external calls to transfer tokens and mint a new membership token before emitting the UserJoinedDAO event. This allows an attacker to reenter the contract during the execution of the external calls and manipulate its state.

Vulnerability Details: What are external calls?

External calls are calls to other contracts or functions that are outside of the current contract. In the case of the joinDAO function, the external calls are:

  • IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees)

  • IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), daoMembershipAddress, tierPrice - platformFees)

  • IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), tierIndex, 1)

These external calls are made to transfer tokens and mint a new membership token.

What is the problem with making external calls before emitting the event?

The problem is that making external calls before emitting the event allows an attacker to reenter the contract during the execution of the external calls. This is because the external calls are executed before the event is emitted, which means that the contract's state is not yet updated to reflect the changes made by the external calls.

How can an attacker reenter the contract?

An attacker can reenter the contract by calling the joinDAO function again during the execution of the external calls. This can be done by exploiting the fact that the external calls are made before the event is emitted.

For example, an attacker can call the joinDAO function to join a DAO, and then reenter the contract by calling the joinDAO function again during the execution of the external calls. This can allow the attacker to manipulate the state of the contract, potentially allowing them to steal tokens or disrupt the normal functioning of the contract.

What is the impact of reentering the contract?

The impact of reentering the contract can be severe. An attacker can manipulate the state of the contract, potentially allowing them to:

  • Steal tokens

  • Disrupt the normal functioning of the contract

  • Gain unauthorized access to the contract's functionality

Impact: Let's break down each of these potential impacts of reentering the contract:

Steal tokens

If an attacker can reenter the contract, they may be able to manipulate the state of the contract to steal tokens from other users. For example, they could:

  • Transfer tokens from other users' accounts to their own account

  • Manipulate the contract's token balances to make it appear as though they have more tokens than they actually do

  • Use the contract's functionality to mint new tokens and transfer them to their own account

This could result in significant financial losses for the users of the contract, as well as damage to the contract's reputation and trustworthiness.

Disrupt the normal functioning of the contract

Reentering the contract could also allow an attacker to disrupt the normal functioning of the contract, potentially causing problems for users who rely on the contract's functionality. For example, they could:

  • Prevent users from being able to join or leave the DAO

  • Interfere with the contract's ability to process transactions or execute functions

  • Cause the contract to enter an inconsistent or invalid state, requiring manual intervention to recover

This could result in significant inconvenience and frustration for users, as well as potential financial losses if the contract is unable to function correctly.

Gain unauthorized access to the contract's functionality

Finally, reentering the contract could allow an attacker to gain unauthorized access to the contract's functionality, potentially allowing them to perform actions that they should not be able to perform. For example, they could:

  • Use the contract's administrative functions to modify the contract's state or behavior

  • Access sensitive information or data stored in the contract

  • Use the contract's functionality to perform malicious actions, such as stealing tokens or disrupting the normal functioning of the contract

This could result in significant security risks for the contract and its users, as well as potential financial losses or reputational damage.

Overall, the impact of reentering the contract can be severe, and it's essential to take steps to prevent reentrancy attacks and ensure the security and integrity of the contract.

Proof of Concept Code:

pragma solidity ^0.8.22;
contract Attacker {\
address public membershipFactoryAddress;
constructor(address _membershipFactoryAddress) public {
membershipFactoryAddress = _membershipFactoryAddress;
}
function attack() public {
// Lock the contract to prevent reentrancy
bool locked = MembershipFactory(membershipFactoryAddress).locked();
// Call the createNewDAOMembership function to create a new DAO membership
MembershipFactory(membershipFactoryAddress).createNewDAOMembership(DAOInputConfig("mydao", "0x...", "0x..."), TierConfig[](1, 100, "0x..."));
// Check if the contract is still locked
require(!locked, "Contract is still locked");
}

}

In this proof of concept, the attacker creates a contract called Attacker that has a constructor that takes the address of the MembershipFactory contract as input. The attack function in the Attacker contract calls the createNewDAOMembership function in the MembershipFactory contract to create a new DAO membership. However, before calling the createNewDAOMembership function, the attacker checks whether the contract is still locked by calling the locked function in the MembershipFactory contract. If the contract is still locked, the attacker can reenter the contract by calling the getENSAddress function in the MembershipFactory contract. This can cause the contract to execute unintended code and potentially lead to security vulnerabilities.

Tools Used: VS code

Recommendations: Here's an example of how to fix the reentrancy vulnerability using a reentrancy lock:

pragma solidity ^0.8.22;
contract MembershipFactory {
// ...
bool private locked;
function createNewDAOMembership(DAOInputConfig memory daoConfig, TierConfig[] memory tierConfigs) public {
// Lock the contract to prevent reentrancy
locked = true;
// Make the external call to initialize the new DAO membership
proxy = new TransparentUpgradeableProxy(membershipImplementation, address(proxyAdmin), abi.encodeWithSignature(initialize(string,string,string,address,address), daoConfig.ensname, OWP, baseURI, _msgSender(), daoConfig.currency));
// Unlock the contract
locked = false;
// ...
}
// ...
}

By using a reentrancy lock, we can prevent an attacker from reentering the contract and exploiting the vulnerability.

This is a Solidity contract that implements a reentrancy lock to prevent reentrancy attacks. Here's a breakdown of the code:

Reentrancy Lock

The contract has a private variable locked of type bool that is used to implement a reentrancy lock. The lock is used to prevent an attacker from reentering the contract and exploiting the vulnerability.

createNewDAOMembership Function

The createNewDAOMembership function is a public function that creates a new DAO membership. The function takes two parameters: daoConfig of type DAOInputConfig and tierConfigs of type TierConfig[].

Locking the Contract

Before making the external call to initialize the new DAO membership, the contract locks itself by setting the locked variable to true. This prevents an attacker from reentering the contract and exploiting the vulnerability.

Making the External Call

The contract makes an external call to initialize the new DAO membership using the TransparentUpgradeableProxy contract. The call is made with the initialize function, which takes several parameters, including the DAO's ENS name, OWP, base URI, sender's address, and currency.

Unlocking the Contract

After making the external call, the contract unlocks itself by setting the locked variable to false. This allows the contract to be reentrant again.

Benefits of the Reentrancy Lock

The reentrancy lock provides several benefits, including:

  • Prevents Reentrancy Attacks: The lock prevents an attacker from reentering the contract and exploiting the vulnerability.

  • Ensures Contract Integrity: The lock ensures that the contract's state is not modified unexpectedly, which helps to maintain the contract's integrity.

  • Improves Security: The lock improves the security of the contract by preventing an attacker from exploiting the vulnerability.

Example Use Case

Here's an example use case for the createNewDAOMembership function:

contract MyDAO {
// ...
function createMembership() public {
// Create a new DAO membership
MembershipFactory factory = MembershipFactory(address);
factory.createNewDAOMembership(DAOInputConfig("mydao", "0x...", "0x..."), TierConfig[](1, 100, "0x..."));
}
}

In this example, the MyDAO contract creates a new DAO membership using the createNewDAOMembership function. The function is called with the required parameters, including the DAO's ENS name, OWP, base URI, sender's address, and currency.

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.