SC01:2026 - アクセス制御の脆弱性 (Access Control Vulnerabilities)

説明

Improper access control describes any situation where a smart contract does not rigorously enforce who may invoke privileged behavior, under which conditions, and with which parameters. In modern DeFi systems this goes far beyond a single onlyOwner modifier. Governance contracts, multisigs, guardians, proxy admins, and cross‑chain routers all participate in enforcing who can mint or burn tokens, move reserves, reconfigure pools, pause or unpause core logic, or upgrade implementations. If any of these trust boundaries are weak or inconsistently applied, an attacker may be able to impersonate a privileged actor or cause the system to treat an untrusted address as if it were authorized.

Few areas to focus on:

  • Ownership / admin controls (e.g., onlyOwner, governor, multisig)

  • Upgrade and pause mechanisms (proxy admins, guardians)

  • Fund movement and accounting (mint/burn, pool reconfiguration, fee routing)

  • Cross-chain or cross-module trust boundaries (bridges, vault routers, L2 messengers)

Attackers exploit:

  • Missing modifiers or role checks on sensitive functions

  • Incorrect assumption of msg.sender (e.g., via delegate calls or meta-transactions)

  • Unprotected initialization / re-initialization of contracts or proxies

  • Privilege confusion across modules (e.g., off-by-one checks, mistaken trusted addresses)

When combined with other issues (e.g., logic bugs, upgradeability flaws), access control failures can lead to full protocol compromise.

事例 (脆弱なコントラクト)

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

contract LiquidityPoolVulnerable {
    address public owner;
    mapping(address => uint256) public balances;

    constructor() {
        owner = msg.sender;
    }

    // Anyone can set a new owner – critical access control bug
    function transferOwnership(address newOwner) external {
        owner = newOwner; // No access control
    }

    // Intended to be called only by the owner to rescue tokens
    function emergencyWithdraw(address to, uint256 amount) external {
        // Missing: require(msg.sender == owner)
        require(balances[address(this)] >= amount, "insufficient");
        balances[address(this)] -= amount;
        balances[to] += amount;
    }
}

Issues:

  • transferOwnership is callable by anyone, allowing arbitrary takeover.

  • emergencyWithdraw lacks any access control, effectively granting any caller the ability to drain the contract’s balance.

事例 (ロールベースのアクセス制御での修正バージョン)

Security Improvements:

  • Explicit RBAC: DEFAULT_ADMIN_ROLE, GOVERNANCE_ROLE, and GUARDIAN_ROLE.

  • Only trusted roles can reconfigure privileges or perform emergency withdrawals.

  • Clear separation between configuration (governance) and emergency response (guardian).

2025 ケーススタディ

ベストプラクティスと緩和策

Robust access control starts with using battle‑tested primitives such as OpenZeppelin’s Ownable and AccessControl rather than bespoke role systems. Privileged roles should be few, clearly documented, and ideally held by well‑secured multisigs or governance modules instead of EOAs. Initialization routines for upgradeable contracts must be locked after first use, with initializer/reinitializer guards and explicit versioning to prevent re‑initialization attacks. Upgrade paths for proxies and core components should be tightly controlled and observable, with events emitted for every privilege change or upgrade so that off‑chain monitoring can quickly detect abuse. Finally, access control policies should be encoded in tests, fuzzing properties, and, where possible, formal specifications, verifying properties such as “no unprivileged address can ever drain funds or seize admin control.”

Last updated