Skip to content
On this page

「ABI Smuggling」:脆弱性を突いて、失われたトークンを取り戻せ!

Damn Vulnerable DeFi v4 の「ABI Smuggling」チャレンジへようこそ!このチャレンジでは、許可されたアカウントのみが特定の操作を実行できる、保護された金庫(Vault)に隠された 100 万 DVT トークンを奪還することがミッションです。開発チームは、この金庫から全ての資金を盗むことが可能だという責任ある開示を受けています。あなたには、これらのトークンを安全な回収アカウントに転送する、この脆弱性を解き明かす役割が託されています。

脆弱性の核心:「AuthorizedExecutor」の巧妙な罠

このチャレンジの鍵を握るのは AuthorizedExecutor コントラクトです。このコントラクトは、特定の「アクションID」に基づいて、誰がどの操作を実行できるかを管理する、汎用的な認証メカニズムを内蔵しています。execute 関数は、actionData の最初の 4 バイト(関数のセレクタ)を読み取り、それを msg.sendertarget と組み合わせて getActionId 関数でハッシュ化します。このハッシュ値が permissions マッピングに存在しない場合、操作は拒否されます。

しかし、ここで巧妙な落とし穴があります。execute 関数は、actionData の先頭にあるセレクタしか認識しません。もし、execute 関数自体に渡されるデータ内に、別の関数の呼び出しをエンコードしたデータ(actionData)を埋め込むことができれば、どうなるでしょうか?

「SelfAuthorizedVault」の落とし穴

SelfAuthorizedVault は、AuthorizedExecutor を継承し、金庫の機能を提供します。withdraw 関数や sweepFunds 関数といった、トークンを安全に管理するための重要な関数が備わっています。特筆すべきは、_beforeFunctionCall 関数です。この関数は、execute 関数から呼び出され、targetaddress(this)(つまり SelfAuthorizedVault 自身)である場合にのみ、処理を許可します。これにより、外部から直接 SelfAuthorizedVault の関数を呼び出すことは防がれています。

脆弱性の突所:ABI エンコーディングの盲点

ここで「ABI Smuggling」という名の所以が現れます。AuthorizedExecutor.execute 関数は、渡された actionData の先頭 4 バイトをセレクタとして解釈するという動作をします。

もし、私たちが AuthorizedExecutor.execute 関数に、第一引数として SelfAuthorizedVault 自身のアドレスを指定し、第二引数として、SelfAuthorizedVaultsweepFunds 関数を呼び出すための ABI エンコードされたデータを含む、さらにその外側に AuthorizedExecutor.execute 関数の呼び出しをエンコードしたデータ を渡したらどうなるでしょうか?

具体的には、以下のような構造のデータを作成します。

  1. 外部呼び出し: AuthorizedExecutor.execute 関数を呼び出すためのデータ。
  2. 内部呼び出し: AuthorizedExecutor.execute 関数の actionData 引数として、SelfAuthorizedVault.sweepFunds 関数を呼び出すためのデータ。

この際、AuthorizedExecutor.execute 関数に渡される actionData先頭 4 バイトは、AuthorizedExecutor.execute 自身のセレクタになります。しかし、AuthorizedExecutor.execute 関数は、その actionData の奥深くにある sweepFunds のセレクタを、本来の target (SelfAuthorizedVault) の実行対象として解釈する ことになります。

そして、AuthorizedExecutor.execute_beforeFunctionCall が呼び出される際、targetSelfAuthorizedVault 自身であるため、TargetNotAllowed() エラーは発生しません。

さらに、AuthorizedExecutor.execute 関数では、permissions をチェックする際に getActionId(selector, msg.sender, target) を使用します。ここで、selectorAuthorizedExecutor.execute 自身のセレクタですが、msg.senderplayer であり、targetvault です。この組み合わせで許可されている必要があります。

解決への道筋

この脆弱性を突くためには、以下のステップを踏みます。

  1. AuthorizedExecutor.execute を呼び出すための、ABI エンコードされたトランザクションデータを作成します。
    • AuthorizedExecutor.execute のセレクタを含めます。
    • target として address(vault) を指定します。
    • actionData として、SelfAuthorizedVault.sweepFunds 関数を呼び出すための ABI エンコードされたデータを含めます。
      • SelfAuthorizedVault.sweepFunds のセレクタ。
      • recovery アドレス。
      • address(token)
  2. これらのデータを巧みに組み合わせ、ABI エンコーディングのトリックを使用します。
    • AuthorizedExecutor.executeactionData 引数に、SelfAuthorizedVault.sweepFunds を呼び出すためのデータを含めます。
    • さらに、ABI エンコーディングの仕組みを利用して、この「ネストされた」データを AuthorizedExecutor.execute の引数として渡す際のオフセットなどを正確に調整します。
    • Forge の Address.functionCall を利用して、この精密に構築されたトランザクションを vault に送信します。

期待される結果

この巧妙な ABI スモーグリング攻撃が成功すると、AuthorizedExecutor.execute 関数は、内部にエンコードされた SelfAuthorizedVault.sweepFunds の呼び出しを、本来の意図通りに SelfAuthorizedVault 自身に実行させます。これにより、SelfAuthorizedVault は、target != address(this) というチェックを回避し、sweepFunds 関数を実行して、金庫内の全ての DVT トークンを回収アカウントに転送します。

さあ、この ABI の盲点を突いて、金庫からトークンを救い出しましょう!

Built with AiAda