「借りた家の鍵で、自分の家の金庫が開けられる?」スマートコントラクトの危険な罠、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を安全に使うための「プロキシパターン」の仕組みを紐解いていきます。また一緒に学んでいきましょう!
コメント