【実務・中級編】 スマートコントラクト監査における静的解析ツールSlitherの活用と検知ルールカスタマイズ – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

Slitherの向こう側:スマートコントラクト監査の「盲点」を突くカスタム検出器の作り方

現場のエンジニア諸君。GitHubで星の数が多い静的解析ツールを走らせて、「脆弱性は見つかりませんでした」というレポートに安心してはいないか?

断言する。汎用的なツールが検知できるのは、教科書に載っているような初歩的なバグだけだ。 本当に恐ろしいのは、ビジネスロジックの隙間を縫う「プロジェクト固有の欠陥」だ。今回は、Slitherを単なるツールから最強の武器へと昇華させる、カスタム検出器の実装と、実務で使える防御論を展開する。

—

1. Slitherの限界を理解する:なぜ汎用ツールだけでは足りないのか

Slitherは非常に優秀だ。再入可能攻撃(Reentrancy)や、初期化されていないストレージ変数などを瞬時に見つけてくれる。しかし、例えば「特定の権限を持つユーザーが、特定の条件下でだけ、特定の関数を呼び出せるべき」というような、そのプロジェクト独自のビジネス要件までは理解していない。

攻撃者は、ソースコードを一行ずつ読み込み、開発者が「まさかここを操作する奴はいないだろう」と油断しているロジックの「隙間」を狙う。この「隙間」を埋めるには、Slitherの基盤である SlithIR(中間表現)を理解し、独自の検知ルールをPythonで記述する必要がある。

—

2. カスタム検出器の作成:ビジネスロジックの「穴」を塞ぐ

Slitherのカスタム検出器は、Pythonで slither.detectors.AbstractDetector を継承して作成する。以下は、特定の機密関数に対して、「適切な修飾子(Modifier)が付与されているか」をチェックする実戦的なテンプレートだ。

from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Modifier

class OnlyOwnerMissingDetector(AbstractDetector):
    """
    重要関数に 'onlyOwner' 修飾子が付いているかチェックするカスタム検出器
    """
    ARGUMENT = "check-owner"
    HELP = "重要関数にアクセス制限がありません"
    IMPACT = DetectorClassification.HIGH
    CONFIDENCE = DetectorClassification.HIGH

    def _detect(self):
        results = []
        for contract in self.compilation_unit.contracts_derived:
            for function in contract.functions:
                # 'transfer' や 'withdraw' など、保護すべき関数名を指定
                if function.name in ["withdrawFunds", "setAdmin"]:
                    # 修飾子リストの中に 'onlyOwner' が存在するか確認
                    modifiers = [m.name for m in function.modifiers]
                    if "onlyOwner" not in modifiers:
                        info = [self, "機密関数 ", function, " に所有者制限がありません!\n"]
                        results.append(self.generate_result(info))
        return results

このように、「何が起きたらシステムが崩壊するか」をコードの形に落とし込むことが、監査の質を分ける境界線だ。

—

3. 実践:攻撃シナリオと防御的コーディング

多くのエンジニアが「コントラクトだけ」を気にしているが、現実の攻撃はWebフロントエンドやAPI経由の入力値検証の甘さを突いてくる。

例えば、フロントエンドからの入力値(amount)をそのままコントラクトの payable 関数に渡すような設計は自殺行為だ。以下は、JavaScript側でのバリデーションと、コントラクト側での堅牢な実装サンプルだ。

フロントエンド(JavaScript)でのガード実装

// 入力値が負の数やゼロでないことを厳格にチェック
async function withdraw(amount) {
    const amountBigInt = BigInt(amount);
    if (amountBigInt <= 0n) {
        throw new Error("無効な送金額です。");
    }
    // トランザクション送信前にCLIやUIで再確認を挟むのが鉄則
    await contract.withdrawFunds(amountBigInt);
}

コントラクト(Solidity)での防御的実装

// require文は必ずメッセージを添え、かつ処理の先頭(Checks)に書く
function withdrawFunds(uint256 amount) public onlyOwner {
    // 1. Checks: 状態確認
    require(amount > 0, "Amount must be greater than zero");
    require(address(this).balance >= amount, "Insufficient contract balance");

    // 2. Effects: 状態更新(再入攻撃対策として先に更新する)
    uint256 balanceBefore = address(this).balance;
    
    // 3. Interactions: 外部呼び出し
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}

—

4. 現場のセキュリティリサーチャーからの助言

最後に、ツール以上に重要な「泥臭い習慣」を伝授する。

1. Slitherのレポートを無視するな: どんなに微小な警告(LOW/INFOレベル)でも、それがロジックの綻びを示唆していることは多い。
2. 監査は「コード」ではなく「意図」を読む: コードが正しく動くかではなく、「このコードはどう悪用できるか」を常に考えろ。
3. IAMとクラウド設定を見直せ: Web3プロジェクトのバックエンドが侵害されると、秘密鍵が抜かれる。クラウドの IAM ポリシーは最小権限の原則を徹底し、ローテーション可能なキー管理サービス(KMS)を使用すること。

設定例(AWS IAM Policy 最小権限):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["kms:Sign"],
      "Resource": "arn:aws:kms:region:account-id:key/key-id",
      "Condition": {
        "StringEquals": { "kms:ViaService": "ec2.amazonaws.com" }
      }
    }
  ]
}

セキュリティとは、ツールを導入して終わりという「点」の作業ではない。コードの設計思想からデプロイ環境までを俯瞰する「線」の継続的な努力だ。Slitherのカスタムルールを育て、君たちのプロジェクトを鉄壁に守り抜いてほしい。

何かあれば、またこの現場で会おう。健闘を祈る。

コメント

タイトルとURLをコピーしました