背景
2025 年 7 月 9 日,據(jù)慢霧 MistEye 安全監(jiān)控系統(tǒng)監(jiān)測,知名去中心化交易平臺 GMX (@GMX_IO) 遭攻擊,損失價值超 4200 萬美元的資產(chǎn)。慢霧安全團隊第一時間對該事件展開分析,并將結(jié)果整理如下:
(https://x.com/SlowMist_Team/status/1942949653231841352)
相關(guān)信息
攻擊者地址:
https://arbiscan.io/address/0xdf3340a436c27655ba62f8281565c9925c3a5221
攻擊合約地址:
https://arbiscan.io/address/0x7d3bd50336f64b7a473c51f54e7f0bd6771cc355
存在漏洞的合約地址:
https://arbiscan.io/address/0x3963ffc9dff443c2a94f21b129d429891e32ec18
攻擊交易:
https://arbiscan.io/tx/0x03182d3f0956a91c4e4c8f225bbc7975f9434fab042228c7acdc5ec9a32626ef
根本原因
此次攻擊的根本原因主要有兩點:
這兩個因素直接影響了總資產(chǎn)規(guī)模(AUM) 的計算,進而導(dǎo)致 GLP 代幣價格被操控。
攻擊者利用這個設(shè)計缺陷,通過 Keeper 在執(zhí)行訂單時會啟用 `timelock.enableLeverage` 的特性(創(chuàng)建大額空單的必要條件),以重入的方式成功創(chuàng)建大額空頭頭寸,操縱全局平均價格和全局空頭頭寸規(guī)模,在單筆交易中人為抬高 GLP 的價格,并通過贖回操作獲利。
攻擊前置準(zhǔn)備
1. 攻擊者首先用兩筆前置交易為攻擊合約先創(chuàng)建一個做多的倉位,之后再創(chuàng)建一個能讓 Keeper 執(zhí)行的減倉訂單。
2. Keeper 收到減倉訂單后會調(diào)用 PositionManager 合約的executeDecreaseOrder 函數(shù)為攻擊合約執(zhí)行減倉操作。其中會先調(diào)用 Timelock 合約的 enableLeverage 函數(shù)去啟用 Vault 合約中的 _isLeverageEnabled 為 true,這是后續(xù)攻擊流程中能直接創(chuàng)建空頭倉位的必要條件。
之后會調(diào)用 OrderBook 合約的 executeDecreaseOrder 函數(shù)執(zhí)行減倉的具體邏輯,在對攻擊合約的倉位進行更新之后,會將減倉得到的抵押品代幣轉(zhuǎn)移給攻擊合約。而因為抵押品代幣是 WETH,所以會將 WETH 換回 ETH 后再轉(zhuǎn)給攻擊合約,這就觸發(fā)了攻擊合約中構(gòu)造的 fallback 函數(shù)來進行后續(xù)的重入操作。
3. 攻擊合約的 fallback 函數(shù)會先將 3001 枚 USDC 轉(zhuǎn)移給 Vault 合約,并調(diào)用 Vault 合約的 increasePosition 函數(shù)開 30 倍的 WBTC 空單。之后再對該倉位創(chuàng)建對應(yīng)的平倉訂單,以供 Keeper 后續(xù)能繼續(xù)調(diào)用。
在increasePosition 函數(shù)中,會先調(diào)用其內(nèi)部的 _validate 函數(shù)檢查 isLeverageEnabled 是否為 true,而只有在 Keeper 執(zhí)行訂單的時候才會先啟用 _isLeverageEnabled,使得這一步中的檢查通過。直接調(diào)用該函數(shù)的話是無法成功開倉的,這也就說明了攻擊者創(chuàng)建減倉訂單的目的是讓 Keeper 執(zhí)行減倉訂單的同時能通過 fallback 函數(shù)重入去調(diào)用 increasePosition 函數(shù),直接創(chuàng)建空頭倉位。
此外,由于通過 increasePosition 函數(shù)進行開倉時會更新 globalShortAveragePrices 的值,而通過 decreasePosition 函數(shù)減倉或平倉時并不會對 globalShortAveragePrices 的值進行更新。所以攻擊者可以循環(huán)不斷地用 3000 枚 USDC 不斷創(chuàng)建做空訂單后立即再讓 Keeper 平空,以此操控 globalShortAveragePrices 的值,最終使得該值比正常 WBTC 的價格低了大概 57 倍。(需要注意這里所有對 WBTC 做空和平空的操作都是在 Keeper 執(zhí)行減倉訂單時重入進去完成的)
第一次通過重入做空 WBTC 時的全局空頭平均價格(GlobalShortAveragePrice) 為 108757787000274036210359376021024492,發(fā)起正式攻擊交易前最后一次做空 WBTC 時的全局空頭平均價格(GlobalShortAveragePrice) 為 1913705482286167437447414747675542,前后相差約為 57 倍。
正式攻擊步驟
1. Keeper 收到最后一次重入時創(chuàng)建的減倉訂單后會調(diào)用 OrderBook 合約的 executeDecreaseOrder 函數(shù)執(zhí)行減倉,將減倉獲得 ETH 轉(zhuǎn)移給攻擊合約時會觸發(fā)其 fallback 函數(shù)。
2. 其中會先從 Uniswap 閃電貸借出 753.8 萬枚 USDC,接著調(diào)用 RewardRouterV2 合約的 mintAndStakeGlp 函數(shù),用其中 600 萬枚 USDC 鑄造 412.9 萬枚 GLP 代幣并進行質(zhì)押。
鑄造時獲取的 AumInUsdg 值為 46942248263037264990037614。
WBTC 的全局空頭倉位規(guī)模(globalShortSizes) 和全局空頭平均價格(getGlobalShortAveragePrice) 分別為 15373061114092959107000000000000000 和 1913705482286167437447414747675542。
3. 緊跟著,攻擊合約調(diào)用 Vault 合約的 increasePosition 函數(shù),向 Vault 中轉(zhuǎn)入 USDC 并創(chuàng)建價值 1538.5 萬的大額 WBTC 空頭倉位。
在increasePosition 函數(shù)的最后會用這部分新開倉的大額空頭頭寸去更新全局空頭倉位規(guī)模,使得全局空頭倉位規(guī)模(globalShortSizes) 被立即增大。
4. 接著攻擊合約在完成大額空頭頭寸建倉后立即調(diào)用了 unstakeAndRedeemGlp 函數(shù)進行 GLP 代幣的解除質(zhì)押并贖回。
然而這里可以看到只贖回了 38.6 萬枚的 GLP 卻燃燒掉了 973.1 萬枚的 USDG 代幣,并且最后轉(zhuǎn)移了 88 枚 WBTC 給攻擊合約,這是為什么呢?我們繼續(xù)跟進到 GlpManager 合約的 _removeLiquidity 函數(shù)中:
當(dāng)用戶執(zhí)行贖回 GLP 代幣的操作時,該函數(shù)會使用以下公式計算應(yīng)燃燒的 USDG 代幣數(shù)量:usdgAmount = _glpAmount * aumInUsdg / glpSupply。之后會將這些 USDG 轉(zhuǎn)移到 Vault 中,賣出換回用戶需要贖回的資產(chǎn)(WBTC)。而其中 aum 的計算方式大致為:
aum = ((totalPoolAmounts – totalReservedAmounts) * price) + totalGuaranteedUsd + GlobalShortLoss(全局空頭倉位虧損) – GlobalShortProfits(全局空頭倉位盈利) – aumDeduction
而由于在上一步創(chuàng)建了大額的空頭頭寸,使得全局空頭倉位規(guī)模被增大,且由于全局平均價格(getGlobalShortAveragePrice) 在之前被操控得遠(yuǎn)低于正常價格,所以這部分空頭頭寸是虧損的(即 hasProfit 為 false),從而讓 GlobalShortLoss(全局空頭倉位虧損)增加了好幾百倍,導(dǎo)致 AUM 被操控放大(aum + delta)。最終攻擊者利用被操控后的 AUM 贖回了超出正常數(shù)量的資產(chǎn)。
6. 最后,攻擊者繼續(xù)利用被操控后的 AUM 不斷調(diào)用 unstakeAndRedeemGlp 函數(shù)來贖回 Vault 中的其他資產(chǎn)來獲利。
MistTrack 分析
據(jù)鏈上反洗錢與追蹤工具 MistTrack 分析,初始攻擊者地址(0xdf3340a436c27655ba62f8281565c9925c3a5221) 獲利超 4200 萬美元,包括:
資金轉(zhuǎn)移情況大致梳理如下:
1. 初始攻擊者地址在 Arbitrum 上獲利后,迅速將 WETH、WBTC、DAI 等資產(chǎn)轉(zhuǎn)移至中轉(zhuǎn)地址(0x99cdeb84064c2bc63de0cea7c6978e272d0f2dae),并使用 CoW Swap、Across Protocol、Stargate Finance、Mayan Finance 等多個 DEX 和跨鏈橋?qū)①Y產(chǎn)進行兌換、跨鏈轉(zhuǎn)移到 Ethereum。
2. 攻擊者主要通過 CoW Swap 將 USDC 兌換為 DAI,再換為 ETH。
3. 大量資產(chǎn)最終都被兌換成 ETH,目前共計 11,700 枚 ETH 流入地址(0x6acc60b11217a1fd0e68b0ecaee7122d34a784c1)。
值得注意的是,攻擊者的初始資金來自 Tornado Cash 于 7 月 7 日轉(zhuǎn)入的 2 ETH,然后再通過 Mayan Finance 將 2 ETH 跨鏈到 Arbitrum,為整個攻擊流程提供初始 Gas。
截至目前,余額情況如下:
我們將持續(xù)對資金進行監(jiān)控。
總結(jié)
本次攻擊的核心在于攻擊者利用了 Keeper 系統(tǒng)執(zhí)行訂單時會啟用杠桿,以及做空時會更新全局平均價格而平空卻不會更新的這兩個特性,通過重入攻擊創(chuàng)建大額空頭頭寸,操控了全局空頭平均價格和全局空頭倉位規(guī)模的值,從而直接放大了 GLP 價格來贖回獲利。
慢霧安全團隊建議項目方應(yīng)該根據(jù)自身業(yè)務(wù)邏輯對核心函數(shù)增加重入鎖的保護,并嚴(yán)格限制單一因素對價格的直接影響程度。此外,應(yīng)當(dāng)對項目的合約代碼加強審計與安全測試,從而避免類似情況的發(fā)生。