50822 sc high deployer can cpgrade arctoken to malicious implementation and steal all user funds

Submitted on Jul 28th 2025 at 20:31:45 UTC by @holydevoti0n for Attackathon | Plume Network

  • Report ID: #50822

  • 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

  • Permanent freezing of funds

Description

Brief/Intro

The ArcTokenFactory contract gives the deployer DEFAULT_ADMIN_ROLE which allows a malicious deployer to upgrade the deployed ArcToken to any malicious implementation.

Vulnerability Details

When deploying a new ArcToken using the createToken function, the deployer is granted the DEFAULT_ADMIN_ROLE and the UPGRADER_ROLE is assigned to the factory.

https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcTokenFactory.sol#L193

    function createToken(
        string memory name,
        string memory symbol,
        uint256 initialSupply,
        address yieldToken,
        string memory tokenUri,
        address initialTokenHolder,
        uint8 decimals
    ) external returns (address) {
       ...
        // Grant the DEFAULT_ADMIN_ROLE to the deployer
        token.grantRole(token.DEFAULT_ADMIN_ROLE(), msg.sender);
        token.grantRole(token.ADMIN_ROLE(), msg.sender);
        token.grantRole(token.MANAGER_ROLE(), msg.sender);
        token.grantRole(token.YIELD_MANAGER_ROLE(), msg.sender);
        token.grantRole(token.YIELD_DISTRIBUTOR_ROLE(), msg.sender);
        token.grantRole(token.MINTER_ROLE(), msg.sender);
        token.grantRole(token.BURNER_ROLE(), msg.sender);
        token.grantRole(token.UPGRADER_ROLE(), address(this));

        ...
    }

Protocol assumes that only the factory can upgrade the token. However, as shown above, the deployer is granted the DEFAULT_ADMIN_ROLE. This role implicitly grants full control over all other roles, including the UPGRADER_ROLE. As a result, the deployer can unilaterally upgrade the token contract.

A malicious deployer would only need to perform the following steps:

  • Use grantRole() to assign themselves the UPGRADER_ROLE.

  • Call upgradeToAndCall() to point the proxy to a malicious implementation.

  • Use the malicious implementation to drain funds, mint tokens, or anything else.

Impact

References:

  • https://docs.openzeppelin.com/contracts/5.x/access-control

Proof of Concept

Context

  • ArcToken is deployed by the factory.

  • The deployer is granted the DEFAULT_ADMIN_ROLE.

  • The deployer calls grantRole() to assign themselves the UPGRADER_ROLE.

  • The deployer calls upgradeToAndCall() to point the proxy to a malicious implementation.

  • At this point, attacker can do anything with the users' funds (withdraw/burn/freeze).

1

Attack steps

  • Create an ArcToken via the factory.

  • Grant UPGRADER_ROLE to the attacker (authorized via DEFAULT_ADMIN_ROLE).

  • Deploy a malicious ArcToken implementation.

  • Call upgradeToAndCall() on the proxy to switch to the malicious implementation.

  • Use the malicious implementation to drain/burn/freeze funds.

PoC test (add to ArcTokenFactory.t.sol)

Run:

Output:

Was this helpful?