作者:九九&Kong&Lisa

編輯:Liz

背景

2025 年 7 月 9 日,據慢霧 MistEye 安全監控系統監測,知名去中心化交易平臺 GMX (@GMX_IO) 遭攻擊,損失價值超 4200 萬美元的資產。慢霧安全團隊第一時間對該事件展開分析,並將結果整理如下:

(https://x.com/SlowMist_Team/status/1942949653231841352)

相關信息

攻擊者地址:

https://arbiscan.io/address/0xdf3340a436c27655ba62f8281565c9925c3a5221

攻擊合約地址:

https://arbiscan.io/address/0x7d3bd50336f64b7a473c51f54e7f0bd6771cc355

存在漏洞的合約地址:

https://arbiscan.io/address/0x3963ffc9dff443c2a94f21b129d429891e32ec18

攻擊交易:

https://arbiscan.io/tx/0x03182d3f0956a91c4e4c8f225bbc7975f9434fab042228c7acdc5ec9a32626ef

根本原因

此次攻擊的根本原因主要有兩點:

  • 一是 GMX v1 在創建空頭頭寸時會更新全局空頭平均價格(globalShortAveragePrices) 的值,而關掉空頭頭寸時卻不會對其進行任何更新。

  • 二是 GMX v1 在處理空頭頭寸時會立即增加全局空頭倉位規模(globalShortSizes)。

這兩個因素直接影響了總資產規模(AUM) 的計算,進而導致 GLP 代幣價格被操控。

攻擊者利用這個設計缺陷,通過 Keeper 在執行訂單時會啓用 `timelock.enableLeverage` 的特性(創建大額空單的必要條件),以重入的方式成功創建大額空頭頭寸,操縱全局平均價格和全局空頭頭寸規模,在單筆交易中人爲擡高 GLP 的價格,並通過贖回操作獲利。

攻擊前置準備

1. 攻擊者首先用兩筆前置交易爲攻擊合約先創建一個做多的倉位,之後再創建一個能讓 Keeper 執行的減倉訂單。

2. Keeper 收到減倉訂單後會調用 PositionManager 合約的executeDecreaseOrder 函數爲攻擊合約執行減倉操作。其中會先調用 Timelock 合約的 enableLeverage 函數去啓用 Vault 合約中的 _isLeverageEnabled 爲 true,這是後續攻擊流程中能直接創建空頭倉位的必要條件。

之後會調用 OrderBook 合約的 executeDecreaseOrder 函數執行減倉的具體邏輯,在對攻擊合約的倉位進行更新之後,會將減倉得到的抵押品代幣轉移給攻擊合約。而因爲抵押品代幣是 WETH,所以會將 WETH 換回 ETH 後再轉給攻擊合約,這就觸發了攻擊合約中構造的 fallback 函數來進行後續的重入操作。

3. 攻擊合約的 fallback 函數會先將 3001 枚 USDC 轉移給 Vault 合約,並調用 Vault 合約的 increasePosition 函數開 30 倍的 WBTC 空單。之後再對該倉位創建對應的平倉訂單,以供 Keeper 後續能繼續調用。

在increasePosition 函數中,會先調用其內部的 validate 函數檢查 isLeverageEnabled 是否爲 true,而只有在 Keeper 執行訂單的時候纔會先啓用 isLeverageEnabled,使得這一步中的檢查通過。直接調用該函數的話是無法成功開倉的,這也就說明了攻擊者創建減倉訂單的目的是讓 Keeper 執行減倉訂單的同時能通過 fallback 函數重入去調用 increasePosition 函數,直接創建空頭倉位。

此外,由於通過 increasePosition 函數進行開倉時會更新 globalShortAveragePrices 的值,而通過 decreasePosition 函數減倉或平倉時並不會對 globalShortAveragePrices 的值進行更新。所以攻擊者可以循環不斷地用 3000 枚 USDC 不斷創建做空訂單後立即再讓 Keeper 平空,以此操控 globalShortAveragePrices 的值,最終使得該值比正常 WBTC 的價格低了大概 57 倍。(需要注意這裏所有對 WBTC 做空和平空的操作都是在 Keeper 執行減倉訂單時重入進去完成的)

第一次通過重入做空 WBTC 時的全局空頭平均價格(GlobalShortAveragePrice) 爲 108757787000274036210359376021024492,發起正式攻擊交易前最後一次做空 WBTC 時的全局空頭平均價格(GlobalShortAveragePrice) 爲 1913705482286167437447414747675542,前後相差約爲 57 倍。

正式攻擊步驟

1. Keeper 收到最後一次重入時創建的減倉訂單後會調用 OrderBook 合約的 executeDecreaseOrder 函數執行減倉,將減倉獲得 ETH 轉移給攻擊合約時會觸發其 fallback 函數。

2. 其中會先從 Uniswap 閃電貸借出 753.8 萬枚 USDC,接着調用 RewardRouterV2 合約的 mintAndStakeGlp 函數,用其中 600 萬枚 USDC 鑄造 412.9 萬枚 GLP 代幣並進行質押。

鑄造時獲取的 AumInUsdg 值爲 46942248263037264990037614。

WBTC 的全局空頭倉位規模(globalShortSizes) 和全局空頭平均價格(getGlobalShortAveragePrice) 分別爲 15373061114092959107000000000000000 和 1913705482286167437447414747675542。

3. 緊跟着,攻擊合約調用 Vault 合約的 increasePosition 函數,向 Vault 中轉入 USDC 並創建價值 1538.5 萬的大額 WBTC 空頭倉位。

在increasePosition 函數的最後會用這部分新開倉的大額空頭頭寸去更新全局空頭倉位規模,使得全局空頭倉位規模(globalShortSizes) 被立即增大。

4. 接着攻擊合約在完成大額空頭頭寸建倉後立即調用了 unstakeAndRedeemGlp 函數進行 GLP 代幣的解除質押並贖回。

然而這裏可以看到只贖回了 38.6 萬枚的 GLP 卻燃燒掉了 973.1 萬枚的 USDG 代幣,並且最後轉移了 88 枚 WBTC 給攻擊合約,這是爲什麼呢?我們繼續跟進到 GlpManager 合約的 _removeLiquidity 函數中:

當用戶執行贖回 GLP 代幣的操作時,該函數會使用以下公式計算應燃燒的 USDG 代幣數量:usdgAmount = _glpAmount * aumInUsdg / glpSupply。之後會將這些 USDG 轉移到 Vault 中,賣出換回用戶需要贖回的資產(WBTC)。而其中 aum 的計算方式大致爲:

aum = ((totalPoolAmounts - totalReservedAmounts) * price)  + totalGuaranteedUsd + GlobalShortLoss(全局空頭倉位虧損) - GlobalShortProfits(全局空頭倉位盈利) - aumDeduction

而由於在上一步創建了大額的空頭頭寸,使得全局空頭倉位規模被增大,且由於全局平均價格(getGlobalShortAveragePrice) 在之前被操控得遠低於正常價格,所以這部分空頭頭寸是虧損的(即 hasProfit 爲 false),從而讓 GlobalShortLoss(全局空頭倉位虧損)增加了好幾百倍,導致 AUM 被操控放大(aum + delta)。最終攻擊者利用被操控後的 AUM 贖回了超出正常數量的資產。

6. 最後,攻擊者繼續利用被操控後的 AUM 不斷調用 unstakeAndRedeemGlp 函數來贖回 Vault 中的其他資產來獲利。

MistTrack 分析

據鏈上反洗錢與追蹤工具 MistTrack 分析,初始攻擊者地址(0xdf3340a436c27655ba62f8281565c9925c3a5221) 獲利超 4200 萬美元,包括:

資金轉移情況大致梳理如下:

1. 初始攻擊者地址在 Arbitrum 上獲利後,迅速將 WETH、WBTC、DAI 等資產轉移至中轉地址(0x99cdeb84064c2bc63de0cea7c6978e272d0f2dae),並使用 CoW Swap、Across Protocol、Stargate Finance、Mayan Finance 等多個 DEX 和跨鏈橋將資產進行兌換、跨鏈轉移到 Ethereum。

 2. 攻擊者主要通過 CoW Swap 將 USDC 兌換爲 DAI,再換爲 ETH。

3. 大量資產最終都被兌換成 ETH,目前共計 11,700 枚 ETH 流入地址(0x6acc60b11217a1fd0e68b0ecaee7122d34a784c1)。 

值得注意的是,攻擊者的初始資金來自 Tornado Cash 於 7 月 7 日轉入的 2 ETH,然後再通過 Mayan Finance 將 2 ETH 跨鏈到 Arbitrum,爲整個攻擊流程提供初始 Gas。

截至目前,餘額情況如下:

  • Arbitrum 地址 0xdf3340a436c27655ba62f8281565c9925c3a5221,餘額 10,494,796 Legacy Frax Dollar 和 1.07 ETH;

  • Ethereum 地址 0xa33fcbe3b84fb8393690d1e994b6a6adc256d8a3,餘額 3000 ETH;

  • Ethereum 地址 0xe9ad5a0f2697a3cf75ffa7328bda93dbaef7f7e7,餘額 3000 ETH;

  • Ethereum 地址 0x69c965e164fa60e37a851aa5cd82b13ae39c1d95,餘額 3000 ETH;

  • Ethereum 地址 0x639cd2fc24ec06be64aaf94eb89392bea98a6605,餘額 2700 ETH。

我們將持續對資金進行監控。

總結

本次攻擊的核心在於攻擊者利用了 Keeper 系統執行訂單時會啓用槓桿,以及做空時會更新全局平均價格而平空卻不會更新的這兩個特性,通過重入攻擊創建大額空頭頭寸,操控了全局空頭平均價格和全局空頭倉位規模的值,從而直接放大了 GLP 價格來贖回獲利。

慢霧安全團隊建議項目方應該根據自身業務邏輯對核心函數增加重入鎖的保護,並嚴格限制單一因素對價格的直接影響程度。此外,應當對項目的合約代碼加強審計與安全測試,從而避免類似情況的發生。