「家を建てたのに鍵をかけ忘れた?」スマートコントラクトの命取り、初期化プロキシの脆弱性を徹底解説!
皆さん、こんにちは!セキュリティリサーチャーの筆者です。
今日は、ブロックチェーンの世界で「スマートコントラクト」を開発する際に、絶対に避けて通れない「アップグレード可能コントラクト」の落とし穴についてお話しします。「難しそう…」と思った方も大丈夫。身の回りの防犯に例えて、一つずつ紐解いていきましょう!
—
なぜ「アップグレード」が必要なの?
現実の家を想像してみてください。一度建ててしまったら、後から「やっぱり玄関の向きを変えたいな」と思っても、建て直しは大変ですよね?
でも、もし「家の中に魔法の仕掛けがあって、外観はそのままで中身だけ最新の家具に入れ替えられる」としたらどうでしょう?ブロックチェーンの世界では、これを「プロキシ(代理)パターン」と呼びます。
- プロキシ(代理人): 皆さんが直接やり取りする窓口。
- 実装コントラクト(本体): 実際の機能が詰まっている頭脳。
この仕組みを使えば、バグが見つかった時も、本体だけを新しいものに差し替えることで、プログラムを修正できるんです。便利ですよね!
—
「初期化の忘れ物」という致命的なミス
さて、ここからが本題です。この「家の中身を差し替える」仕組みにおいて、最も危険なのが「初期化関数の未保護」です。
本来、新しい家(コントラクト)を建てたら、持ち主が「これは自分の家です」という所有権を登録するための「鍵の受け渡し」が必要です。これをスマートコントラクトでは initialize 関数で行います。
しかし、この関数に「鍵をかけるガードマン(修飾子)」を付け忘れるとどうなるか……。
泥棒がやってきて、あなたが鍵を受け取る前に、勝手に「この家の持ち主は自分だ!」と宣言できてしまうのです。
これが「未初期化プロキシコントラクト」の脆弱性です。
—
泥棒に入られないための「賢い防犯」
では、コードで具体的に見ていきましょう。まずは「やってはいけない」ダメな例です。
// 警告:これは脆弱なコードの例です!
function initialize(address _owner) public {
// 誰でもこの関数を呼べてしまうため、泥棒が所有権を奪えます
owner = _owner;
}
この状態だと、誰かが先にこの関数を呼び出して、自分のアドレスを owner に設定してしまいます。こうなると、あなたはもうそのコントラクトを操作できなくなります。
対策: initializer 修飾子を使う
「一度だけしか実行できない」という鍵をかけましょう。OpenZeppelinという世界標準のライブラリを使えば、驚くほど簡単です。
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract MyContract is Initializable {
address public owner;
// initializer修飾子を使うことで、一度しか実行できないようになります
function initialize(address _owner) public initializer {
owner = _owner;
}
}
これで、誰かが一度でも initialize を実行したら、それ以降は誰もこの関数を叩けなくなります。これで一安心ですね!
—
コンストラクタを使わない理由
鋭い方はこう思うかもしれません。「あれ?普通のプログラムなら constructor を使うんじゃないの?」と。
実は、プロキシパターンでは constructor(建築時の設計図)は、「本体」には効くけれど、「窓口(プロキシ)」には効かないという特殊な性質があります。そのため、代わりに initialize 関数を使って、後から手動で初期化を行う必要があるのです。
もし、どうしても constructor で初期化したい場合は、以下のようにガードを固めておくのが鉄則です。
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
// 誰かが勝手に初期化するのを防ぐため、デプロイ時に即座にロックをかける
_disableInitializers();
}
—
セキュリティリサーチャーからの教訓
現場でインシデントを見ていると、多くのプロジェクトが「機能の構築」に忙しくて、この「初期の鍵かけ」を疎かにしてしまいます。
1. initializer 修飾子を忘れないこと。
2. デプロイ直後に必ず誰が所有者になったかを確認すること。
3. テスト環境で「誰でも初期化できるか?」を攻撃者の視点で試してみること。
セキュリティは、完璧な要塞を築くことではありません。「泥棒がどこから入るか」を想像し、一つずつ窓に鍵をかけていく、その地道な積み重ねこそが最強の防壁になります。
皆さんの開発するコントラクトが、誰にも壊されない強固なものになりますように。また次の記事でお会いしましょう!
コメント