Project

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

Event-Based Reentrancy in MembershipFactory Contract Allows for Unauthorized Access and Data Manipulation

Summary

The MembershipFactory contract’s functions are vulnerable to event-based reentrancy, as they emit events after external calls. In Ethereum, this pattern can lead to missing event calls and state inconsistencies if a reentrant call is made before the function completes. For instance, a malicious contract could exploit reentrancy vulnerabilities to repeatedly invoke the function in unintended ways, bypassing checks or duplicating actions that rely on correct event logging.

Vulnerability Details

The joinDAO and upgradeTier functions perform external calls before emitting events, creating a vulnerability. In Ethereum, if a function makes an external call and is reentered by a malicious contract, any intended state changes or event emissions may be bypassed, resulting in inaccurate event logs or incorrect state information.

A reentrant call could allow an attacker to manipulate internal state without emitting the corresponding event, creating a scenario where actions are effectively hidden from monitoring mechanisms that rely on events. This issue is exacerbated by the fact that events serve as crucial logs for off-chain systems to track contract activity.

Link to the Affected Code

Impact

Unauthorized state manipulation and potential reentrancy attacks, resulting in unrecorded actions or unintended behaviors. Reentrancy vulnerabilities are well-known attack vectors, especially in complex contracts where external calls and state changes intermingle. In this case, the contract makes multiple external calls, leaving it exposed to reentrant attacks under scenarios where malicious actors identify opportunities to repeatedly call the vulnerable functions.

An attacker exploiting event-based reentrancy could:

  1. Suppress Events: Perform state-altering actions that are not logged, creating mismatches between on-chain state and off-chain records, including DAO membership status or tier upgrades.

  2. Manipulate Contract State: Execute a reentrant call before the original function completes, potentially manipulating contract state in unintended ways, such as duplicating or bypassing actions that are critical to tier management.

  3. Obscure Off-Chain Monitoring: Since many dApps and analytics platforms rely on events to track activity, missing events could cause off-chain systems to be out of sync, potentially leading to financial discrepancies or inaccurate contract usage reports.

Proof of Concept

1. A malicious contract could interact with the joinDAO function, calling it in a reentrant manner:
* First, the contract could call joinDAO, where transferFrom is executed before the UserJoinedDAO event is emitted.
* The reentrant call would trigger the function again before the state and event logging complete, allowing the attacker to duplicate or bypass actions.
2. For upgradeTier, an attacker could repeatedly invoke mint and burn actions without emitting UserJoinedDAO, creating a discrepancy between on-chain actions and event logs.

Tools Used

Manual Review

Recommendations

  • Implement ReentrancyGuard: Apply the OpenZeppelin ReentrancyGuard modifier to functions that make external calls. This ensures only one execution instance per transaction:

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract DAOContract is ReentrancyGuard {
function joinDAO(address daoMembershipAddress, uint256 tierIndex) external nonReentrant {
// Function implementation
}
function upgradeTier(address daoMembershipAddress, uint256 fromTierIndex) external nonReentrant {
// Function implementation
}
}
  • Follow Checks-Effects-Interactions Pattern: Move external calls to the end of the function and perform state changes and event emissions beforehand. This pattern minimizes reentrancy risks by ensuring that all internal logic is handled before any interaction with external contracts.

Emit Events Before External Calls: Ensure all relevant events are emitted before making any external calls, as shown below:
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;
// Effects
daos[daoMembershipAddress].tiers[tierIndex].minted += 1;
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, tierIndex);
// Interactions
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), daoMembershipAddress, tierPrice - platformFees);
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), tierIndex, 1);
}
  • Following these recommendations will harden the contract against reentrancy attacks, ensuring that functions execute safely without unintended reentrant interactions and that events accurately log all user actions.

Updates

Lead Judging Commences

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

Support

FAQs

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