#4 GMXインシデント:クロスコントラクト再入可能攻撃、4年前の防御を回避

#4 GMXインシデント:クロスコントラクト再入可能攻撃、4年前の防御を回避

2025年7月9日、分散型パーペチュアルプラットフォームであるGMXは、Arbitrumネットワーク上のV1コントラクトを標的としたエクスプロイト[1, 2]により、約4200万ドルの損失を被りました。攻撃者はクロスコントラクトのリエントランシー脆弱性を悪用してGLP価格を操作し、その歪んだ価格を利用してGMX V1の流動性プールから原資産を詐取しました。

背景

GMX V1 [3]は、Arbitrum上に展開された分散型パーペチュアル取引プラットフォームです。これにより、ユーザーは複数の暗号資産に対して、レバレッジをかけて、許可なく非保管的な方法でパーペチュアルコントラクトを取引できます。GMX V1は、単一のマルチアセットプール設計を採用しており、サポートされているすべての資産の流動性が統一されたVaultシステムに集約されます。

GMXにおけるポジション管理

GMXにおけるポジション管理は2段階のプロセスです。具体的には、ユーザーはOrderBookまたはPositionRouterコントラクトと対話して、増減注文を作成します。その後、承認されたキーパーアカウントがユーザーの注文を執行し、ShortTrackerコントラクトのグローバルショートデータとVaultコントラクトの対応する状態が更新されます。以下の2つの図は、GMXでのポジション管理のための2つの実行パス(orderbook-executionrouter-executionパス)を示しています。このインシデントでは、攻撃者は両方のパスを使用してグローバルショートデータを操作し、利益を実現しました。

Orderbook-execution パス

Router-execution パス

GMX Vault

GMXのVaultコントラクトは、ユーザーのポジションと資産(例:損益の確定)の管理を担当します。悪意のあるインタラクションを回避するため、Vaultコントラクトは「レバレッジウィンドウ」が開いている場合(すなわち、Vaultコントラクトの変数isLeverageEnabledTimelock.enableLeverage()関数によってtrueに設定されている場合)のみアクセス可能です。この検証により、注文実行は固定パス(orderbook-executionおよびrouter-execution)に従う必要があります。

GMX Short Tracker

ShortTrackerコントラクトは、注文実行中にグローバルショートデータ(例:変数globalShortAveragePrice)の追跡と更新を担当します。orderbook-executionおよびrouter-executionパスに従い、ShortTrackerコントラクトのupdateGlobalShortData()関数は、最新のグローバルショートデータを同期するために、ユーザーの注文を実行する前に呼び出されます。このステップにより、ユーザーのポジションの正確な損益実現が保証されます。

WETHポジションの減少

他の注文とは異なり、orderbook-executionパスを介してWETHポジションを減少させると、OrderBookコントラクトの_transferOutETH()関数が呼び出され、WETHトークンが引き出され、ネイティブETHトークンがユーザーに転送されます。受信者_receiverがコントラクトの場合、sendValue()はコントラクトのfallback()関数をトリガーする可能性があり、潜在的なリエントランシー脆弱性を導入します。このインシデントでは、攻撃者はこのリエントランシー脆弱性を繰り返し悪用して、相当な利益を抽出しました。

GLPトークン

GLP(GMX Liquidity Provider)トークンは、Vaultコントラクト内のさまざまな資産の統合されたシェアを表します。ユーザーはGLPをミントおよびバーンすることで、資産の提供と償還が許可されます。

GLP価格は、以下の式で計算できます。

GLP価格=AUMGLP総供給量\text{GLP価格}= \frac{\text{AUM}}{\text{GLP総供給量}}

AUM=i=asset[0]資産(ΔGlobalShort[i]+AUMOther[i])\text{AUM} = \sum^{資産}_{i = asset[0]}(\Delta_{\text{GlobalShort[i]}} + \text{AUM}_{\text{Other[i]}})

ΔGlobalShort[i]=globalShortSize×(AssetMarketPriceglobalShortAveragePrice)globalShortAveragePrice\Delta_{\text{GlobalShort[i]}} = \frac{ \text{globalShortSize} \times (\text{AssetMarketPrice} - \text{globalShortAveragePrice} ) }{ \text{globalShortAveragePrice} }

ここで:

  • GLP総供給量はGLPトークンの総供給量を示します。
  • AUMは次の2つのコンポーネントで構成されます。
    • ΔGlobalShort\Delta_{\text{GlobalShort}}は、すべてのショートポジションのuPnL(すなわち、未実現損益)を表します。
    • AUMOther\text{AUM}_{\text{Other}}は、LPの提供された流動性に、すべてのロングポジションの未実現uPnLを加えたものです。この項は、損益実現前はインシデント全体を通じてほとんど変化しませんでした。
  • AssetMarketPriceは、原資産の市場価格(USD建て)です。
  • globalShortSizeは、すべてのショートポジションの合計(USD建て)です。
  • globalShortAveragePriceは、集計されたグローバルショートポジションの平均エントリー価格です。

このインシデントでは、攻撃者はglobalShortAveragePriceを歪めてGLP価格を操作し、利益を抽出しました。

脆弱性分析

根本原因は、OrderBookコントラクトにおけるクロスコントラクトのリエントランシー脆弱性です。背景で説明したように、orderbook-executionパスを介してWETHポジションを減少させると、_transferOutETH()を介して受信者への低レベルのフォールバック呼び出しがトリガーされます。この関数にはnonReentrant修飾子がありますが、このガードはOrderBookコントラクト自体のリエントランシーを防ぐだけで、Vaultへのクロスコントラクト呼び出しは防ぎません。

通常の操作では、VaultincreasePosition()PositionRouterおよびPositionManagerを通じてのみ呼び出すことができ、これらは各ポジション変更の前にShortTrackerを呼び出してglobalShortAveragePriceを更新します。攻撃者は、悪意のあるコントラクトを受信者として注文を作成し、フォールバック呼び出し中に直接VaultincreasePosition()を呼び出して、PositionRouter/PositionManagerおよび関連するShortTrackerの更新をバイパスしました。globalShortAveragePriceを更新せずにショートポジションを繰り返し開閉することにより、攻撃者は徐々に変数を歪めました。歪んだ値はGLP価格を膨張させ、攻撃者がGLPをミントおよび償還することで利益を抽出することを可能にしました。

攻撃分析

攻撃者は、グローバルショートデータを操作し、利益を実現するための連続したトランザクションを実行しました。具体的には、このインシデントは「準備」、「価格操作」、「利益実現」の3つのフェーズに分けることができます。関連するすべてのトランザクションは、以下の表にリストされています。

Tx番号 フェーズ 説明 トランザクション 時間(UTC)
1 準備 攻撃コントラクトのデプロイ 0xa4ece5...8cd4c93f 2025年7月9日 12:16:32 PM
2 WETHロングポジション増加注文の作成 0x0b8cd6...e90a4712 2025年7月9日 12:22:28 PM
3 WETHロングポジション増加注文の実行 0x28a000...7bf0beef 2025年7月9日 12:23:23 PM
4 WETHロングポジション減少注文の作成 0x20abfe...decc49af 2025年7月9日 12:24:56 PM
5 価格操作 1 WETHロングポジション減少注文の実行 0x1f00da...6a4a7353 2025年7月9日 12:25:37 PM
6 WBTCショートポジション減少注文の実行 0x222cda…c994464e 2025年7月9日 12:25:43 PM
7 価格操作 2 Tx 5と同様 0xc9a469...221293c2 2025年7月9日 12:26:25 PM
8 Tx 6と同様 0x1cbf25...d853943a 2025年7月9日 12:26:30 PM
9 価格操作 3 Tx 5と同様 0xb58415...3b4cfb0b 2025年7月9日 12:27:22 PM
10 Tx 6と同様 0x5a37ff...cb59c3b7 2025年7月9日 12:27:28 PM
11 価格操作 4 Tx 5と同様 0xff6fe6...377bf108 2025年7月9日 12:28:13 PM
12 Tx 6と同様 0xbd65d6...e0187be6 2025年7月9日 12:28:18 PM
13 価格操作 5 Tx 5と同様 0x105273...19fcdec6 2025年7月9日 12:29:12 PM
14 Tx 6と同様 0x0cdbac...84339fcc 2025年7月9日 12:29:17 PM
15 利益実現 利益の実現 0x03182d....a32626ef 2025年7月9日 12:30:11 PM
16 返金 攻撃者へのメッセージ 0x92a39e...89547380 2025年7月9日 02:04:19 PM
17 GMXプロトコルへの応答 0x1d806c...919feac0 2025年7月11日 06:29:00 AM
18 攻撃者への応答 0x9c4ca9...39fa27fc 2025年7月11日 07:42:17 AM
19 返金 0x62b845...99211841 2025年7月11日 08:04:34 AM
20 返金 0x255d0a...9321b3 2025年7月11日 08:08:27 AM
21 返金 0xceafc3...a6313b22 2025年7月11日 10:17:23 AM

準備フェーズ

  1. (Tx 1) 攻撃者は、注文実行中に資産受信者として使用される攻撃コントラクトをデプロイしました。攻撃コントラクトには、悪意のあるfallback()関数が含まれていました。

  2. (Tx 2) 攻撃者は、OrderBookコントラクトで、攻撃コントラクトに対するWETHロングポジション増加注文を作成しました。

  3. (Tx 3) キーパーが、攻撃者の増加注文(ステップ2で作成)を実行し、攻撃コントラクトのためにWETHロングポジションを開きました。(Tx 3)。

  4. (Tx 4) 攻撃者は、OrderBookコントラクトで、攻撃コントラクトに対するWETHロングポジション減少注文を作成しました。

操作フェーズ

  1. (Tx 5) キーパーが、orderbook-executionパスを介して、攻撃コントラクトの減少WETHロング注文(ステップ4で作成)を実行しました。この実行は、攻撃コントラクトの悪意のあるfallback()関数をトリガーする_transferOutETH()を呼び出しました。

    fallback()は「レバレッジウィンドウ」中に呼び出されたため、攻撃コントラクトはVaultコントラクトと直接対話し、globalShortAveragePriceを更新することなくWBTCショートポジション(increasePosition()経由)を開き、続いてWBTCショートポジションの減少/クローズ注文を作成しました。

  2. (Tx 6) キーパーが、router-executionパスを介して、WBTCショートポジション減少注文(ステップ5で作成)を実行しました。この実行は、PositionRouterコントラクトのフォールバックメカニズムを介して、WETHロングポジション減少注文も作成しました。

    WBTCショートポジションはglobalShortAveragePriceを更新せずに開かれたため、この変数を更新しながらポジションをクローズすると、globalShortAveragePriceの予期せぬ低下が発生しました。

  3. (Tx 7-14) 攻撃者はステップ5-6を4回繰り返しました。その結果、globalShortAveragePrice1.08e35から1.9e33に歪みました。

利益実現フェーズ

  1. (Tx 15) キーパーが、OrderBookコントラクトのリエントランシー脆弱性により、Tx 14で作成された減少WETHロング注文をorderbook-executionパスを介して実行しました。この実行は、攻撃コントラクトの利益実現ロジックをトリガーしました。

    1. フォールバック呼び出しで、攻撃コントラクトはまず7,538,567e18 USDCのフラッシュローンを借りました。

    2. 攻撃コントラクトは、6,000,000e18 USDCを使用して4,129,578e18 GLPをミントするためにmintAndStakeGlp()を呼び出しました。

    3. 攻撃コントラクトは、残りの1,538,567e18 USDCでWBTCショートポジションを開くためにVault.increasePosition()を呼び出しました。WBTCのグローバルショートデータが極端に歪んでいたため、注文実行はAUMを大幅に増幅し、GLP価格を急上昇させました。

    4. 攻撃コントラクトは、Vaultコントラクトの複数の資産でGLPを増幅された価格で償還するためにunstakeAndRedeemGlp()を呼び出しました。

    5. 攻撃コントラクトは、WBTCショートポジションをクローズするためにVault.decreasePosition()を呼び出しました。

    6. 攻撃コントラクトは、Vaultコントラクトのすべての資産を枯渇させるために、ステップ8.b-8.eを4回繰り返しました。

    7. 攻撃コントラクトは、フラッシュローンを返済し、約4200万ドルの利益を得ました。

返金

攻撃者との交渉(Tx 16-18)を経て、攻撃者は最終的に10%のバウンティを受け入れ、残りの盗まれた資産を返還しました(Tx 19-21)。

まとめ

このインシデントは、Arbitrum上のGMX V1に対する多段階エクスプロイトであり、推定4200万ドルの損失につながりました。攻撃者はOrderBookコントラクトのリエントランシー脆弱性を悪用してglobalShortAveragePrice変数を歪め、GLP価格をインフレさせました。操作された価格を活用することで、攻撃者は大量の資産を詐取しました。

主な教訓:

  • クロスコントラクトのリエントランシー: 単一コントラクトのnonReentrant修飾子は、同じシステム内の他のコントラクトへのリエントランシーを防ぐものではありません。一時的なフラグ(例:「レバレッジウィンドウ」)に依存するアクセス制御メカニズムは、フラグがリセットされる前に外部呼び出しが発生した場合にバイパスされる可能性があります。
  • 運用セキュリティ: この攻撃者は3つの異なるフェーズでプロトコルを悪用し、合計15回のトランザクションを実行しました。このインシデントは、リアルタイム監視、迅速なアラート、および効果的な緩和プレイブックの極めて重要な必要性を強調しています。

参考文献

  1. https://x.com/GMX_IO/status/1942955807756165574
  2. https://x.com/GMX_IO/status/1943336664102756471
  3. GMX V1

BlockSecについて

BlockSecは、フルスタックのブロックチェーンセキュリティおよび仮想通貨コンプライアンスプロバイダーです。私たちは、コード監査(スマートコントラクト、ブロックチェーン、ウォレットを含む)、リアルタイムでの攻撃傍受、インシデント分析、不正資金追跡、およびAML/CFT義務の履行を、プロトコルとプラットフォームのライフサイクル全体にわたって支援する製品とサービスを構築しています。

BlockSecは、権威あるカンファレンスで複数のブロックチェーンセキュリティ論文を発表し、DeFiアプリケーションのゼロデイ攻撃を複数報告し、複数のハッキングを阻止して2000万ドル以上を救済し、数十億ドル相当の仮想通貨を確保しています。

Sign up for the latest updates