【入門編】 コントラクトの自己破壊(selfdestruct)による資金凍結 – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

その金庫、横からこじ開けられるかも?「selfdestruct」と資金凍結の罠

こんにちは!セキュリティリサーチャーの視点から、今日はWeb3開発で新人が必ず一度は躓く(そして痛い目を見る)「コントラクトの自己破壊(selfdestruct)」というトピックについてお話しします。

「ブロックチェーンって改ざんできないから安全なんじゃないの?」と思っている方、実はコントラクトの「残高」に関しては、思わぬ盲点があるんです。家の鍵を頑丈にしても、隣の壁を壊されたら意味がないのと一緒です。一歩ずつ、紐解いていきましょう!

—

1. そもそも「selfdestruct」って何?

スマートコントラクトには、自分自身をブロックチェーン上から消し去り、残っている資金を指定したアドレスに強制送金する selfdestruct という関数が存在します(※最近のアップデートで非推奨になりつつありますが、既存のコントラクトには無数に眠っています)。

これを「悪意ある攻撃者」が使うとどうなるか。本来、お金を受け取る機能を持たないはずのコントラクトに、強制的にETH(イーサリアム)を送りつけることができるのです。

家の鍵に例えると…

あなたの家(スマートコントラクト)の玄関には「お金を受け取るためのゲート」がなく、鍵がかかっているとします。しかし、攻撃者は「隣の壁を破壊(selfdestruct)」して、あなたの家のリビングに無理やり現金を投げ込むことができます。

「えっ、お金が増えるならいいじゃん!」と思いましたか? ここが落とし穴なんです。

—

2. なぜ「強制送金」が脆弱性になるのか

多くの開発者は、コントラクトの残高を確認する際、以下のようにコードを書きます。

// 悪い例:残高に依存するロジック
function withdrawAll() public {
    // もし攻撃者が無理やりETHを送りつけていたら、残高が予期せぬ数値になり、
    // ロジックが狂って資金が引き出せなくなる可能性があります。
    require(address(this).balance == totalDeposited, "残高が一致しません!");
    payable(msg.sender).transfer(address(this).balance);
}

もし攻撃者が selfdestruct を使って 1 wei でも余計に送ると、address(this).balance と totalDeposited が一致しなくなり、withdrawAll 関数が二度と動かなくなります。 これが「資金凍結」の正体です。

—

3. どうすれば防げるの?

対策はシンプルです。「コントラクトの残高を信じすぎるな」ということです。

対策:残高をロジックの判定に使わない

自分の家の中にある金庫の重さを測るのではなく、「誰がいくら預けたか」という帳簿(マッピング)を自分で管理しましょう。

// 良い例:帳簿で管理する
mapping(address => uint256) public balances;

function deposit() public payable {
    // 預け入れた額を自分の帳簿に記録する
    balances[msg.sender] += msg.value;
}

function withdraw() public {
    uint256 amount = balances[msg.sender];
    require(amount > 0, "預け入れがありません");
    
    // 帳簿を先にゼロにする(再入攻撃対策)
    balances[msg.sender] = 0;
    
    // 自分の帳簿の分だけ送金する
    payable(msg.sender).transfer(amount);
}

このように、address(this).balance(外からいくらでも操作可能な物理的な残高)を判定に使わず、自分自身が確実に把握している balances という帳簿だけを信頼するのが、Web3セキュリティの鉄則です。

—

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

「たかが残高チェック」と侮ってはいけません。IoTの世界でも、センサーの値をそのまま制御ロジックに使うと、外から磁石を近づけられて数値を誤魔化されるのと同じです。「外部からの入力(今回は強制送金)で、システムの前提条件が簡単に覆る場所はないか?」と常に疑う癖をつけてください。

最初は難しく感じるかもしれませんが、こうやって一つずつ「泥棒の手口」を知ることで、あなたの書くコードはより強固なものになっていきます。

次は「再入攻撃(Reentrancy)」という、さらに巧妙な泥棒の手口について解説する予定です。準備はいいですか?また次回の記事でお会いしましょう!

コメント

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