Project

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

Insufficient Access Control in MembershipFactory Allows Unauthorized Modification of DAO Configuration

Summary

There is a significant security risk in the MembershipFactory contract where an account with EXTERNAL_CALLER role can bypass critical membership constraints and arbitrarily modify any DAO's configuration. Since the constructor grants both DEFAULT_ADMIN_ROLE and EXTERNAL_CALLER to msg.sender, and there's no additional authorization check in updateDAOMembership, this effectively means anyone with EXTERNAL_CALLER role can modify any DAO's configuration regardless of ownership. This can lead to unauthorized manipulation of DAO structures, potential theft of value through tier modifications, and compromise of DAO governance mechanisms.

The issue lies in insufficient access control in the updateDAOMembership function. While the function checks for EXTERNAL_CALLER role, it fails to validate whether the caller has legitimate authority over the specific DAO being modified. The constructor shows that EXTERNAL_CALLER role is granted at deployment, but the update function doesn't implement any additional checks to verify if the caller is the actual DAO owner or has legitimate authority to modify a specific DAO's configuration.

Current vulnerable implementation:

https://github.com/Cyfrin/2024-11-one-world/blob/main/contracts/dao/MembershipFactory.sol#L100

function updateDAOMembership(string calldata ensName, TierConfig[] memory tierConfigs)
external onlyRole(EXTERNAL_CALLER) returns (address) {
address daoAddress = getENSAddress[ensName];
// No validation of caller's relationship to the DAO
// Only checks EXTERNAL_CALLER role
...
}

Compared to DAO creation which establishes ownership:

userCreatedDAOs[_msgSender()][daoConfig.ensname] = address(proxy);

Recommended Fix

function updateDAOMembership(string calldata ensName, TierConfig[] memory tierConfigs)
external onlyRole(EXTERNAL_CALLER) returns (address) {
address daoAddress = getENSAddress[ensName];
require(daoAddress != address(0), "DAO does not exist.");
require(userCreatedDAOs[_msgSender()][ensName] == daoAddress, "Not DAO owner");
DAOConfig storage dao = daos[daoAddress];
if(dao.daoType == DAOType.SPONSORED){
require(tierConfigs.length == TIER_MAX, "Invalid tier count.");
}
uint256 totalMembers = 0;
for (uint256 i = 0; i < tierConfigs.length; i++) {
if (i < dao.tiers.length) {
require(tierConfigs[i].amount >= dao.tiers[i].minted, "New amount below minted tokens");
tierConfigs[i].minted = dao.tiers[i].minted;
}
totalMembers += tierConfigs[i].amount;
}
require(totalMembers <= dao.maxMembers, "Sum of tier amounts exceeds maxMembers");
delete dao.tiers;
for (uint256 i = 0; i < tierConfigs.length; i++) {
dao.tiers.push(tierConfigs[i]);
}
dao.noOfTiers = tierConfigs.length;
return daoAddress;
}

The fix adds crucial ownership validation by checking userCreatedDAOs mapping to ensure only the original DAO creator can modify its configuration, while maintaining the role-based access control and existing validation logic.

Updates

Lead Judging Commences

0xbrivan2 Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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