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

説明

不適切なアクセス制御は、誰がどのような 条件 下でどのような パラメータ を用いて、特権的な動作を実行できるかを厳格に強制していない状況を指します。現代の DeFi システムでは、これは単一の onlyOwner 修飾子をはるかに超えています。ガバナンスコントラクト、マルチシグ、ガーディアン、プロキシ管理者、クロスチェーンルーターはすべて、トークンの発行や破棄、リザーブの移動、プールの再構成、コアロジックの一時停止や再開、実装のアップグレードをできる人物の強制に関与しています。これらの信頼境界のいずれかが脆弱であったり一貫性なく適用されていると、攻撃者は特権を持つアクターになりすましたり、信頼できないアドレスを認可済みであるかのようにシステムが処理するようにできる可能性があります。

注目する領域は以下のとおりです。

  • 所有権 / 管理者制御 (例: onlyOwner、ガバナー、マルチシグ)

  • アップグレードと一時停止のメカニズム (プロキシ管理者、ガーディアン)

  • 資金の移動と会計 (発行/破棄、プール再構成、料金ルーティング)

  • クロスチェーンまたはクロスモジュールの信頼境界 (ブリッジ、Vault ルーター、L2 メッセンジャー)

攻撃者は以下を悪用します。

  • 機密性の高い関数での 修飾子やロールチェックの欠落

  • msg.sender の誤った想定 (例: デリゲート呼び出しやメタトランザクション経由)

  • コントラクトやプロキシの 保護されていない初期化 / 再初期化

  • モジュール間の 権限の混乱 (例: off-by-one チェック、信頼済みアドレスの誤り)

他の問題 (ロジックバグ、アップグレード可能性の欠陥など) と組み合わせると、アクセス制御の不良はプロトコル全体の侵害につながる可能性があります。

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

// 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;
    }
}

問題点:

  • transferOwnership は誰でも呼び出し可能であり、任意の乗っ取りが可能となります。

  • emergencyWithdraw はアクセス制御を欠いており、事実上、任意の呼び出し元にコントラクトの残高を空にする能力を付与しています。

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

セキュリティの改善:

  • 明示的な RBAC: DEFAULT_ADMIN_ROLE, GOVERNANCE_ROLE, GUARDIAN_ROLE

  • 信頼できるロールのみが権限を再構成したり緊急引き落としを実行できます。

  • 構成 (ガバナンス) と緊急対応 (ガーディアン) の間を明確に分離しています。

2025 ケーススタディ

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

堅牢なアクセス制御は、独自のロールシステムではなく、OpenZeppelin の OwnableAccessControl といった実績のあるプリミティブを使用することから始まります。特権ロールは少数に留め、明確に文書化し、理想的には EOA ではなく、十分にセキュアなマルチシグまたはガバナンスモジュールによって保持されるべきです。アップグレード可能なコントラクトの初期化ルーチンは初回使用後にロックし、再初期化攻撃を防ぐために initializer/reinitializer ガードと明示的なバージョン管理を行う必要があります。プロキシとコアコンポーネントのアップグレードパスは厳密に管理され、監視可能であるべきで、すべての特権変更やアップグレードに対してイベントを発行することで、オフチェーン監視が不正使用を迅速に検出できます。最後に、アクセス制御ポリシーはテストに組み込まれ、プロパティをファジングし、可能であれば形式仕様で、「特権を持たないアドレスが資金を流用したり、管理制御を奪取したりできない」といったプロパティを検証すべきです。

Last updated