51197 sc high arc token owner can take upgrader role for themselves lockout the factory and upgrade the contract without the knowledge of the factory
Submitted on Jul 31st 2025 at 21:43:30 UTC by @TeamJosh for Attackathon | Plume Network
Report ID: #51197
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcTokenFactory.sol
Impacts:
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Escalation of privilege
Description
Brief/Intro
Anyone can create an ArcToken from the factory; the creator of the ArcToken gets the default admin role and the factory itself gets the upgrader role and stores the implementation contract. This means that all upgrades to an ArcToken can only go through the ArcTokenFactory admin. However, the ArcToken owner can revoke the Upgrader Role from ArcTokenFactory and also give themselves this role. By doing this they can upgrade the token to whatever they want while the factory will still think it has a legitimate implementation contract.
Vulnerability Details
The contract ArcToken uses AccessControlUpgradeable. This contract contains public functions grantRoles and revokeRoles that allow the default admin to grant and revoke any role. With this capability the default admin (who is the owner of the ArcToken) can revoke the upgraderRole access of the ArcTokenFactory, thereby blocking the ArcTokenFactory admin from performing upgrades.
The ArcToken admin can also upgrade the ArcToken themselves to a malicious contract. The factory will still store the original (or another) implementation contract address in its mapping, causing the factory to report an incorrect implementation for that token.
Code excerpt showing the factory upgrade and implementation mapping update:
/**
* @dev Upgrades a token to a new implementation
* @param token Token address to upgrade
* @param newImplementation Address of the new implementation
*/
function upgradeToken(address token, address newImplementation) external onlyRole(DEFAULT_ADMIN_ROLE) {
FactoryStorage storage fs = _getFactoryStorage();
// Ensure the token was created by this factory
if (fs.tokenToImplementation[token] == address(0)) {
revert TokenNotCreatedByFactory();
}
// Ensure the new implementation is whitelisted
bytes32 codeHash = _getCodeHash(newImplementation);
if (!fs.allowedImplementations[codeHash]) {
revert ImplementationNotWhitelisted();
}
// Perform the upgrade (this assumes the token implements UUPSUpgradeable)
UUPSUpgradeable(token).upgradeToAndCall(newImplementation, "");
// Update the implementation mapping
@-> fs.tokenToImplementation[token] = newImplementation;
emit TokenUpgraded(token, newImplementation);
}Impact Details
References
https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcTokenFactory.sol#L185
https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcTokenFactory.sol#L279
Proof of Concept
Was this helpful?