【入門編】 デリゲートコール(delegatecall)の危険性とコンテキストの汚染 – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

「借りた家の鍵で、自分の家の金庫が開けられる?」スマートコントラクトの危険な罠、delegatecallを解剖する

こんにちは!セキュリティの世界へようこそ。今日は、ブロックチェーン開発の現場で「便利だけど、使い方を間違えると一瞬で全てを失う」と言われる、恐ろしい機能delegatecall(デリゲートコール)についてお話しします。

「難しそう…」と身構える必要はありません。まずは、私たちの身近な「家の鍵」の話から始めましょう。

—

1. なぜ「delegatecall」が危ないのか?(泥棒の視点)

想像してみてください。あなたは、自分専用の「金庫付きの家」を持っています。この金庫には、あなたの全財産が入っています。

通常、あなたは自分の鍵で金庫を開けますよね。ところが、delegatecallという機能を使うと、「他人の家の鍵を借りて、自分の家の金庫を開ける」という不思議なことができるようになります。

「場所」が同じなら、中身も自分のだと勘違いする

delegatecallの仕組みを一言で言うと、「他人のコードを借りてきて、自分の家の場所で動かす」というものです。

ここが一番の盲点です。
あなたの家の金庫の場所が「スロット1番」だとします。借りてきた他人のコードが「スロット1番を書き換えろ!」という命令を持っていたらどうなるでしょう?

他人は「自分の家のスロット1番」を書き換えるつもりでも、あなたの家で実行された瞬間に、あなたの家の金庫(スロット1番)が書き換わってしまうのです。これが「コンテキストの汚染」と呼ばれる、資産流出の最大の原因です。

—

2. 実際にコードで見てみよう

新人エンジニアがやりがちな、「危ない書き方」を例に挙げます。

// 攻撃を受ける側のコントラクト(あなたの家)
contract MyVault {
    address public owner; // スロット0番
    uint256 public balance; // スロット1番

    function updateBalance(address _impl, uint256 _amount) public {
        // ここでdelegatecallを呼び出すと、
        // 相手のコードが「自分のコンテキスト」で動いてしまう!
        _impl.delegatecall(abi.encodeWithSignature("set(uint256)", _amount));
    }
}

そして、これが「借りてくる」はずの外部コントラクトです。

// 外部コントラクト(他人のコード)
contract Malicious {
    uint256 public fakeVariable; // スロット0番

    function set(uint256 _val) public {
        // このコードは「スロット0番」を書き換える命令です
        fakeVariable = _val; 
    }
}

何が起きたのか?

MyVaultからMaliciousをdelegatecallすると、Maliciousのset関数は「スロット0番(fakeVariable)」を書き換えようとします。しかし、MyVaultのコンテキストでは、スロット0番はfakeVariableではなく、owner(あなたの家の所有権)です。

その結果、あなたの家の所有権が書き換えられ、攻撃者に金庫を乗っ取られてしまうのです。これが「ストレージレイアウトの不一致」による悲劇です。

—

3. どうすれば防げるの?(一歩ずつ対策を学ぼう)

この罠を避けるための基本ルールは、とてもシンプルです。

対策1:ストレージレイアウトを完全に一致させる

もし外部のコードを使うなら、相手と全く同じ順番で変数を用意してください。でも、これは管理が大変でミスが起きやすいです。

対策2:可能な限りdelegatecallを避ける

「どうしても使わなければいけない理由」がない限り、使わないのが一番の防犯です。代わりに、安全なライブラリや標準的なパターン(OpenZeppelinなどの監査済みコントラクト)を利用しましょう。

対策3:コントラクトの「所有者」を保護する

delegatecallを使う場合は、重要な変数(所有権など)が上書きされないよう、Proxyパターンなどの信頼できるアーキテクチャを採用することが鉄則です。

—

最後に:セキュリティは「疑うこと」から始まる

セキュリティリサーチャーとして現場にいると、多くのインシデントが「便利な機能の仕組みを深く理解しないまま使った」ことで起きています。

delegatecallは非常に強力なツールですが、同時に「他人の家で自分の金庫を開けさせる」という危うい綱渡りです。

  • 「このコードは本当に自分の家のレイアウトと合っているか?」
  • 「この外部コントラクトは信頼できるか?」

一歩立ち止まって、この2つを自分に問いかけてみてください。今日学んだことを意識するだけで、あなたの書くスマートコントラクトは、誰にも破れない「強固な金庫」に一歩近づくはずです。

次回の記事では、このdelegatecallを安全に使うための「プロキシパターン」の仕組みを紐解いていきます。また一緒に学んでいきましょう!

コメント

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