SC10:2026 - Proxy & Upgradeability Vulnerabilities

Description

Proxy and upgradeability vulnerabilities describe any situation where a smart contract uses an upgradeable architecture (proxy, beacon, or implementation-swapping pattern) and the upgrade path, initialization, or admin controls are misdesigned or misconfigured. Upgradeable contracts separate a proxy (which holds state and delegates calls) from an implementation (which contains logic). When upgradeability is improperly secured, attackers can hijack the proxy admin or upgrade role to deploy malicious implementations, re-initialize contracts to seize ownership, or bypass critical checks in initialization or migration steps.

This affects all contract types that use upgradeability: DeFi (lending, vaults, DEXes), NFTs (collections, marketplaces), DAOs (governance, treasuries), bridges (messengers, asset contracts), and L2/cross-chain systems. Common patterns include Transparent Proxy, UUPS (EIP-1822), Beacon Proxy, and custom router-implementation designs. On non-EVM chains, analogous upgrade mechanisms exist (e.g., Move modules, Solana program upgrades) with similar trust and initialization risks.

Few areas to focus on:

Attackers exploit:

These issues often overlap with access control (SC01) but warrant separate attention due to the systemic impact of proxy and upgrade mechanisms.

Example (Vulnerable Upgradeable Proxy Admin)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract VulnerableProxyAdmin {
    address public admin;
    address public implementation;

    constructor(address _implementation) {
        // Critical: no way to set custom admin; implicitly trusts deployer logic
        admin = msg.sender;
        implementation = _implementation;
    }

    function upgrade(address newImplementation) external {
        // Missing: access control (only admin) and sanity checks
        implementation = newImplementation;
    }
}

Issues:

Example (Safer Upgradeability Pattern)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";

contract SafeProxyAdmin is Ownable {
    address public implementation;

    event Upgraded(address indexed newImplementation);

    error InvalidImplementation();

    constructor(address _implementation) {
        _setImplementation(_implementation);
        _transferOwnership(msg.sender);
    }

    function _setImplementation(address _implementation) internal {
        if (_implementation == address(0)) revert InvalidImplementation();
        implementation = _implementation;
    }

    function upgrade(address newImplementation) external onlyOwner {
        _setImplementation(newImplementation);
        emit Upgraded(newImplementation);
    }
}

Security Improvements:

Initialization & Re-Initialization Risks

Initialization functions (e.g., initialize(), initializer modifiers in OpenZeppelin) are critical in upgradeable patterns. Common pitfalls:

Basic example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract VulnerableLogic {
    address public owner;

    // Missing initializer guard
    function initialize(address _owner) external {
        owner = _owner;
    }
}

If used behind a proxy without proper initialization control, attackers can call initialize via the proxy and set themselves as owner, taking over the protocol.

2025 Case Studies

Best Practices & Mitigations