【入門編】 アクセス制御の不備(Unprotected Function Call) – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

玄関の鍵をかけ忘れていませんか?スマートコントラクトの「アクセス制御」の基本

こんにちは!セキュリティの世界へようこそ。今日は、ブロックチェーン開発で最も基本的でありながら、実は被害が絶えない「アクセス制御の不備(Unprotected Function Call)」についてお話しします。

「スマートコントラクトにバグがある」と聞くと、なんだか複雑な数式や暗号解読を想像するかもしれませんね。でも、実は多くの重大なハッキング事件は、「誰でも入れる状態の玄関」を放置していたことが原因なんです。

今日は、家の防犯に例えながら、一緒にセキュリティの第一歩を踏み出してみましょう!

—

なぜ「誰でも触れる」状態が危ないのか?

想像してみてください。あなたは大切な金庫を買ってきて、自分の家の庭に置きました。しかし、その金庫には鍵穴がなく、通りすがりの誰でも「開ける」ボタンを押せば中身を取り出せる状態だったらどうでしょう?

ブロックチェーンの世界では、スマートコントラクトの関数がこれに当たります。

本来、コントラクトの「資金を引き出す」「管理者を変更する」といった重要な操作は、持ち主(オーナー)だけができるべきですよね。しかし、コード上で「この人はオーナーですよ」というチェックを怠ると、世界中の悪意あるボットや攻撃者が、あなたの資産を根こそぎ持っていってしまいます。

—

実際にコードで見てみよう(脆弱な例)

まずは、「やってはいけない例」を見てみましょう。以下のようなコードを書くと、誰でもこのコントラクトの資金を好きなだけ引き出せてしまいます。

// 危険なコードの例
contract WeakVault {
    uint256 public balance;

    // 資金を入金する関数
    function deposit() public payable {
        balance += msg.value;
    }

    // 資金を引き出す関数
    // 注意:誰でも実行できてしまいます!
    function withdrawAll() public {
        payable(msg.sender).transfer(address(this).balance);
    }
}

この withdrawAll() 関数には、「誰が実行しているか」をチェックする仕組みが一切ありません。これでは、泥棒が堂々と玄関から入ってきて、金庫を空にするのを見ていることしかできませんよね。

—

「門番(Modifier)」を配置しよう

では、どうすればいいのでしょうか? 答えは簡単です。「門番」を配置すればいいのです。

Solidity(スマートコントラクトを書く言語)には、modifier(修飾子)という強力な機能があります。これは、関数が実行される前に必ずチェックを行い、条件を満たさない場合は処理を止めてくれる「ガードマン」のような存在です。

一番よく使われる onlyOwner という門番を実装してみましょう。

// 安全なコードの例
contract SecureVault {
    address public owner;
    uint256 public balance;

    // コントラクトが作られた時に、デプロイした人をオーナーに設定
    constructor() {
        owner = msg.sender;
    }

    // 門番(修飾子)の定義
    modifier onlyOwner() {
        // もし実行者がオーナーでなければ、エラーを出して止める
        require(msg.sender == owner, "あなたには権限がありません!");
        _; // チェックを通ったら、本来の関数を実行する
    }

    // 門番を設定した引き出し関数
    function withdrawAll() public onlyOwner {
        payable(msg.sender).transfer(address(this).balance);
    }
}

このコードの何が素晴らしいのか?

1. constructor(コンストラクタ): 最初の一回だけ実行され、コントラクトを作った本人を owner として登録します。
2. require 文: 「もし実行者がオーナーじゃなかったら、ここで処理を終了してね」という強力なブレーキです。
3. modifier の適用: function withdrawAll() public onlyOwner と書くことで、「この関数を使うには、まずは onlyOwner のチェックをクリアしてね」と宣言しています。

これで、たとえ泥棒が withdrawAll() を呼び出そうとしても、コントラクトは「あなた、誰ですか?」と問いかけ、実行を拒否するようになります。

—

セキュリティリサーチャーからのアドバイス

開発の現場では、急いでいる時ほど、こうしたチェックを忘れがちです。「あとで実装しよう」と思った機能が、実はサービス全体の崩壊を招くこともあります。

以下のポイントを、開発のチェックリストに加えてみてください。

  • 「誰がこの関数を呼べるべきか?」を常に自問する: 全員が呼んでいいのか、オーナーだけか、あるいは特定の役割(Role)を持つ人だけか。
  • 権限の管理をシンプルに: Ownable(OpenZeppelinなどの標準ライブラリ)を使うと、今回紹介したような門番を自分で書かなくても、安全で堅牢なコードを再利用できます。
  • テストで「他人」を装う: 開発中、あえて自分とは違うアカウント(テスト環境の別アドレス)で関数を叩いてみてください。エラーが出て弾かれることが確認できれば、その機能は無事に守られています。

まとめ:セキュリティは「当たり前」の積み重ね

スマートコントラクトの脆弱性は、魔法のような高度なテクニックで突かれることばかりではありません。むしろ、今日お話ししたような「鍵のかけ忘れ」のような単純なミスが、大きな被害につながることがほとんどです。

「自分の書いたコードは、自分の家の玄関である」。そう意識するだけで、あなたの書くプログラムは驚くほど堅牢になります。

一歩ずつ、丁寧に。まずは、あなたのコードの関数一つひとつに「門番」がいるか確認することから始めてみませんか? あなたのその意識が、Web3の世界をより安全で信頼できる場所に変えていくはずです。

コメント

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