Summary
A user can interact with the dao without completing the KYC process.
The DAO says that,
Member: A user that has completed the signup and KYC process
The whole project does not check whether the user has completed the KYC.
Yes, users will be linked with OWPIdentity after they complete KYC, and their KYC info will be removed from the site,
but the OWPIdentity does not have any functionality and the MembershipFactory does not import anything from OWPIdentity to verify the user's KYC status.
If you see other DAO projects where the user has to complete the signup and KYC process, then you can see on their smart contract codebase they are checking for KYC status, something like that.👇
require(_getKYCStatus(msg.sender), "'sender' address not KYC'd");
Vulnerability Details
https://github.com/Cyfrin/2024-11-one-world/blob/main/contracts/dao/MembershipFactory.sol#L55C1-L94C6
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.");
}
​
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);
}
function joinDAO(address daoMembershipAddress, uint256 tierIndex) external {
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;
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);
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, tierIndex);
}
Impact
Users without completing the KYC process can createNewDAOMembership, joinDAO and many more
which totally violates the main Principle of this DAO
Member: A user that has completed the signup and KYC process
Tools Used
Manually Reviewed
Recommendations
KYC status logic can be implemented on the OWPIdentity as per the developer's wish.
Implement file or function that verifies the user's KYC status.
Implement KYC status check on functions like: joinDAO, createNewDAOMembership, upgradeTierand other necessary functions something like this:
require(_getKYCStatus(msg.sender), "'sender' address not KYC'd");