こんにちは!新人IT担当者の皆さん、あるいは「最近ブロックチェーンやスマートコントラクトの仕組みに触れ始めたよ!」という開発者の皆さん、日々の業務や学習本当にお疲れ様です。
新しい技術をキャッチアップするのって、覚えることがたくさんあってワクワクする半面、ちょっぴり不安になりますよね。「セキュリティの勉強もしなきゃいけないけれど、専門用語が難しそう……」と感じている方もご安心ください。今日は身近な「お家の鍵」に例えながら、スマートコントラクトのセキュリティで最も重要な「アクセス制御の不備によるオーナー権限の奪取」について、一歩ずつ優しく紐解いていきましょう!
—
1. 家の鍵に例える「スマートコントラクトのオーナー権限」
まずは、ブロックチェーン上で動くプログラム「スマートコントラクト」の世界を、私たちの現実世界に置き換えて考えてみましょう。
あなたが新しく一軒家を建てたとします。その家には、あなたの大切な財産や、みんなで共有する宝箱(コントラクトの資金や重要な設定)が置いてありますよね。当然、その家の玄関の鍵を開け閉めできるのは、持ち主である「あなた(オーナー)」だけです。
もし、この玄関の鍵が壊れていたり、誰でも簡単に合鍵を作れる状態になっていたらどうでしょうか?
- 最悪のシナリオ: 通りすがり見知らぬ泥棒が勝手にドアを開け、家の中の宝箱をすべて持ち去ってしまう。さらに、家の権利書を書き換えて、泥棒がその家の「新しいオーナー」になってしまう!
現実の世界なら、こんなことが起きたら一大事ですし、警察や鍵の業者さんに大慌てで連絡しますよね。
実は、ブロックチェーン上のスマートコントラクトでも、これと全く同じことが起きてしまうリスクがあります。それが今回テーマにする「アクセス制御の不備(オーナー権限の奪取)」という脆弱性なんです。
—
2. 攻撃者はどうやって「マスターキー」を奪うのか?
スマートコントラクトには、OpenZeppelin社などが提供する便利なライブラリ(Ownable など)があり、これを使うことで簡単に「オーナーだけが実行できる特別な関数(特権関数)」を作ることができます。例えば、コントラクトの中身を停止させたり、資金を引き出したりする関数ですね。
しかし、プログラムを書く人間がうっかりミスをしてしまうと、この「マスターキー」が誰でも触れる状態になってしまいます。
実際の脆弱なコード例を見てみましょう。Solidityという言語で書かれたコードです。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// 【危険な例】アクセス制御が正しく設定されていないコントラクト
contract VulnerableVault {
address public owner;
// コンストラクタ(最初に一度だけ呼ばれる初期化関数)
constructor() {
// ここでオーナーを設定し忘れたり、誰でも呼べるようにしてしまうと大変!
owner = msg.sender;
}
// 【問題点】誰でも呼べてしまう初期化関数
// 本来なら「オーナーだけが呼べる」ようにすべきですが、修飾子が抜けています
function changeOwner(address _newOwner) public {
// 誰がこの関数を呼んでも、オーナーが書き換わってしまう!
owner = _newOwner;
}
// オーナーだけが引き出せるはずの大切な資金(例)
function withdrawAll() public {
// ここにチェックがないと、泥棒に資金をごっそり持っていかれます
require(msg.sender == owner, "Owner only!");
payable(owner).transfer(address(this).balance);
}
}
このコードの何が危ないか分かりますか? changeOwner という関数に、require(msg.sender == owner); のような「オーナーチェック(鍵の確認)」が書かれていないため、世界中の誰でも、この関数を実行して自分を新しいオーナーに指定できてしまうのです。
泥棒がこの関数を見つけたら、一瞬であなたのコントラクトのオーナー権限を奪い去り、withdrawAll を実行して資産をすべて持ち逃げしてしまいます。ブロックチェーンの世界では、一度盗まれた資産を取り戻すことは極めて困難です。だからこそ、事前の対策が何よりも大切なんですね。
—
3. 安全な家づくり:アクセス制御の実装とマルチシグのすすめ
では、どうすればこのような泥棒の侵入を防げるのでしょうか? 対策は大きく分けて2つあります。
対策①:適切なアクセス修飾子を使う(基本の「鍵」)
OpenZeppelinなどの信頼性の高いライブラリを使い、onlyOwner などのモディファイア(条件チェック)を必ず特権関数に付与しましょう。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
// 【安全な例】OpenZeppelinのOwnableを継承する
contract SecureVault is Ownable {
// コンストラクタでデプロイした人を初期オーナーに設定
constructor() Ownable(msg.sender) {}
// 【対策】onlyOwnerをつけることで、オーナー以外の実行を完全にブロック!
function sensitiveFunction() public onlyOwner {
// ここにオーナーだけが実行できる重要な処理を書きます
}
}
コードの中に onlyOwner と書くだけで、「あ、この関数はオーナー専用なんだな」とスマートコントラクトが自動で判断し、関係ない人からのアクセスを跳ね返してくれます。これなら安心ですね!
対策②:マルチシグ(複数の鍵)によるガバナンスの強化
「でも、もしオーナーであるあなたの秘密鍵(パスワード)自体がハッキングされたらどうしよう?」
そう不安に思ったあなたは素晴らしい着眼点を持っています。一人のおじいさんが家の鍵を全部持っている状態だと、その人が狙われたらおしまいです。
そこで現場でよく使われるベストプラクティスが「マルチシグ(Multi-Signature / 複数署名)」です。
- 仕組みの例: 金庫を開けるのに、5人の理事のうち「3人以上の承認」が必要な仕組みにする。
- メリット: 万が一、1人の開発者のPCがハッキングされて鍵を盗まれても、勝手に資金を持ち出されることはありません。組織全体の合意がないと重要な変更ができないため、圧倒的にセキュリティが向上します。
Gnosis Safe(現 Safe)などのツールを使うと、コードをゼロから書かなくても安全なマルチシグウォレットを構築できるので、実務でスマートコントラクトを運用する際はぜひ導入を検討してみてください。
—
まとめ
今回は、スマートコントラクトにおける「アクセス制御の不備によるオーナー権限の奪取」について、お家の鍵に例えながら解説しました。
1. アクセス制御を忘れると、誰でもオーナーになれてしまう(泥棒に入られる)。
2. onlyOwner などの修飾子を正しく使い、誰がその権限を持つべきか厳格に制限する。
3. 重要な権限は1人だけに持たせず、マルチシグで分散管理してリスクヘッジする。
セキュリティの世界は奥が深いですが、基本の「鍵をちゃんとかける」という意識を持つだけでも、多くのインシデントを防ぐことができます。一歩ずつ、確実に安全な開発スキルを身につけていきましょう!
それでは、また次回の記事でお会いしましょう。あなたの開発ライフが安全で素晴らしいものになりますように!
コメント