Project

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

Privilege Escalation via EXTERNAL_CALLER Role in MembershipFactory

Issue:

The EXTERNAL_CALLER role in the MembershipFactory contract can perform arbitrary calls to external contracts using the callExternalContract function. This introduces a severe security risk, as any account with this role can call any function on any contract, potentially leading to token theft, fund draining, or unauthorized modifications if compromised.

Impact: If the EXTERNAL_CALLER role is compromised, the attacker could perform unauthorized operations on behalf of the DAO, including minting, burning tokens, or transferring funds.

Exploit:

The EXTERNAL_CALLER role in the MembershipFactory contract allows any account with this role to perform arbitrary calls to external contracts. This is a high-risk issue as an attacker with EXTERNAL_CALLER privileges could execute unauthorized calls, potentially transferring tokens or performing unauthorized actions on connected DAOs.

code:

// Location: MembershipFactory.sol
function callExternalContract(address contractAddress, bytes memory data)
external payable onlyRole(EXTERNAL_CALLER) returns (bytes memory)
{
(bool success, bytes memory returndata) = contractAddress.call{value: msg.value}(data);
require(success, "External call failed");
return returndata;
}

Exploit Code:

A malicious user with the EXTERNAL_CALLER role could transfer all tokens from a given ERC20 contract to themselves:

// Example: External contract call to drain tokens
address targetERC20 = /* address of the ERC20 token contract */;
bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", msg.sender, 1e18);
// Assuming EXTERNAL_CALLER privilege, drain tokens
membershipFactory.callExternalContract(targetERC20, data);

Code Change:

Restrict the callExternalContract to only allow safe, whitelisted functions and target contracts:

// Suggested Modification
function callExternalContract(address contractAddress, bytes memory data)
external payable onlyRole(EXTERNAL_CALLER) returns (bytes memory)
{
// Whitelist allowed addresses and function selectors
require(isWhitelistedContract(contractAddress), "Contract not whitelisted");
bytes4 selector = bytes4(data);
require(isAllowedFunctionSelector(selector), "Function not allowed");
(bool success, bytes memory returndata) = contractAddress.call{value: msg.value}(data);
require(success, "External call failed");
return returndata;
}
// Helper functions to manage whitelisting
function isWhitelistedContract(address contractAddress) internal view returns (bool) {
// Implement a whitelist check (mapping or hardcoded addresses)
}
function isAllowedFunctionSelector(bytes4 selector) internal view returns (bool) {
// Implement allowed function selector check
}
POC: // Malicious actor with EXTERNAL_CALLER role can exploit this to drain funds.
function exploitArbitraryCall(address targetContract, uint256 amount) external onlyRole(EXTERNAL_CALLER) {
bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", msg.sender, amount);
callExternalContract(targetContract, data);
}
  • Tools used - VSC, Github

  • Recommendation: Limit the callExternalContract function to only allow specific whitelisted functions and addresses. Add checks to ensure only pre-approved external contracts are called.

Updates

Lead Judging Commences

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

Support

FAQs

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