50713 sc high deployer s default admin role enables self grant of upgrader role bypassing implementation whitelist

Submitted on Jul 27th 2025 at 18:52:08 UTC by @Paludo0x for Attackathon | Plume Network

  • Report ID: #50713

  • 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

Description

Vulnerability Details

ArcTokenFactory::createToken() gives the external caller msg.sender the DEFAULT_ADMIN_ROLE on every new ArcToken.

Because DEFAULT_ADMIN_ROLE is the admin role of all other roles, the deployer can immediately grant themselves UPGRADER_ROLE and execute upgradeTo() on the token proxy installing any implementation, reviewed or not.

This bypasses the factory’s allowedImplementations whitelist and defeats the intended upgrade safety mechanism — any byte-code can be injected as the new ArcToken implementation.

Impact Details

Category is High because the intended safety flow to allow only trusted upgrades is completely bypassed.

With a new implementation the malicious upgrader could do anything, for instance:

  • mint arbitrary number of tokens

  • rug-pull by changing yieldToken to an attacker-owned ERC20 and “distributing” worthless tokens

Proof of Concept

Granting DEFAULT_ADMIN_ROLE to deployer while UPGRADER_ROLE is assigned to factory

In ArcTokenFactory::createToken() the DEFAULT_ADMIN_ROLE is granted to msg.sender while the UPGRADER_ROLE is granted to the same contract address(this):

In AccessControlUpgradeable the DEFAULT_ADMIN_ROLE corresponds to bytes32 0x00, therefore if ADMIN_ROLE has not been changed the DEFAULT_ADMIN_ROLE can grant anyone the role of UPGRADER_ROLE:

In fact in ArcToken only the address corresponding to UPGRADER_ROLE can authorize upgrade of implementation:

The intended address allowed to upgrade the token implementation is the DEFAULT_ADMIN_ROLE of the ArcTokenFactory. This is enforced in ArcTokenFactory::upgradeToken():

Because the deployer gets DEFAULT_ADMIN_ROLE on the token, they can call grantRole(UPGRADER_ROLE, attacker) (or to themselves) and then call UUPSUpgradeable.upgradeTo() to set any implementation — bypassing the allowedImplementations whitelist check performed in the factory-level upgradeToken().

Was this helpful?