The ENS name check in the contract is vulnerable to manipulation cause there is a lack of normalization and sanitization. so an Attackers can bypass the uniqueness check using case sensitivity, and Unicode characters, or whitespace variations. as result this allows them to create visually identical or near-identical DAOs, enabling impersonation and potential exploitation of users.
this check on the function createNewDAOMembership is use to Checks if the ensname provided by the user already maps to an existing address, but If the user provides a manipulated string as an example with Unicode characters, or extra spaces, or different casing, the check will fail to detect the duplicates.
so the use of this check on the fucntion is not robust is weak and can be bypass
the issue is can allow Attackers to create malicious DAOs that visually mimic legitimate ones, misleading users.
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MembershipFactory", function () {
let membershipFactory: any, currencyManager: any, testERC20: any;
let owner: any, addr1: any, addr2: any;
let DAOType: any, DAOConfig: any, TierConfig: any;
beforeEach(async function () {
[owner, addr1, addr2] = await ethers.getSigners();
const CurrencyManager = await ethers.getContractFactory("CurrencyManager");
currencyManager = await CurrencyManager.deploy();
await currencyManager.deployed();
const MembershipERC1155 = await ethers.getContractFactory("MembershipERC1155");
const membershipImplementation = await MembershipERC1155.deploy();
await membershipImplementation.deployed();
const MembershipFactory = await ethers.getContractFactory("MembershipFactory");
membershipFactory = await MembershipFactory.deploy(
currencyManager.address,
owner.address,
"https://baseuri.com/",
membershipImplementation.address
);
await membershipFactory.deployed();
const TestERC20 = await ethers.getContractFactory("OWPERC20");
testERC20 = await TestERC20.deploy("OWP", "OWP");
await testERC20.deployed();
DAOType = { GENERAL: 0, PRIVATE: 1, SPONSORED: 2 };
DAOConfig = {
ensname: "testdao.eth",
daoType: DAOType.GENERAL,
currency: testERC20.address,
maxMembers: 100,
noOfTiers: 3,
};
TierConfig = [
{ price: 300, amount: 10, minted: 0, power: 12 },
{ price: 200, amount: 10, minted: 0, power: 6 },
{ price: 100, amount: 10, minted: 0, power: 3 },
];
await currencyManager.addCurrency(testERC20.address);
});
it("Should bypass uniqueness check with case manipulation", async function () {
await membershipFactory.createNewDAOMembership(DAOConfig, TierConfig);
const maliciousDaoConfig = { ...DAOConfig, ensname: "TestDao.eth" };
await expect(
membershipFactory.createNewDAOMembership(maliciousDaoConfig, TierConfig)
).to.not.be.reverted;
});
it("Should bypass uniqueness check with Unicode manipulation", async function () {
await membershipFactory.createNewDAOMembership(DAOConfig, TierConfig);
const maliciousDaoConfig = { ...DAOConfig, ensname: "testd𝚊o.eth" };
await expect(
membershipFactory.createNewDAOMembership(maliciousDaoConfig, TierConfig)
).to.not.be.reverted;
});
it("Should bypass uniqueness check with trailing whitespace", async function () {
await membershipFactory.createNewDAOMembership(DAOConfig, TierConfig);
const maliciousDaoConfig = { ...DAOConfig, ensname: "testdao.eth " };
await expect(
membershipFactory.createNewDAOMembership(maliciousDaoConfig, TierConfig)
).to.not.be.reverted;
});
});
can mitigate this by normalize the ensname before use and restrict valid characters to a safe subset.
also can use Use hash-based keys in mappings.