#4 GMX事件:跨合约重入漏洞绕过四年旧防护

#4 GMX事件:跨合约重入漏洞绕过四年旧防护

2025年7月9日,去中心化永续合约平台GMX [1, 2] 上部署在Arbitrum网络的V1合约遭到攻击,导致约4200万美元的损失。攻击者利用了跨合约重入漏洞操纵GLP价格,并利用扭曲的价格从GMX V1的流动性池中窃取底层资产。

背景

GMX V1 [3] 是一个部署在Arbitrum上的去中心化永续合约交易平台。它允许用户以无需许可和非托管的方式,交易多资产的杠杆永续合约。GMX V1遵循单一多资产池设计,所有支持资产的流动性聚合到一个统一的Vault系统中。

GMX中的仓位管理

GMX中的仓位管理是一个两步过程。具体来说,用户通过与OrderBookPositionRouter合约交互来创建增加/减少订单。然后,授权的keeper账户执行用户的订单,这将更新ShortTracker合约中的全局空头数据以及Vault合约的相应状态。以下两图展示了在GMX中管理仓位的两个执行路径(orderbook-executionrouter-execution路径)。在此次事件中,攻击者同时利用了这两个路径来操纵全局空头数据并实现利润。

Orderbook-execution 路径

Router-execution 路径

GMX Vault

GMX中的Vault合约负责管理用户的仓位和资产(例如,最终确定盈亏)。为了防止恶意交互,Vault合约仅在“杠杆窗口”打开时(即通过Timelock.enableLeverage()函数将Vault合约中的isLeverageEnabled变量设置为true)才能访问。此验证强制要求订单执行必须遵循固定的路径(orderbook-executionrouter-execution)。

GMX Short Tracker

ShortTracker合约负责在订单执行期间跟踪和更新全局空头数据(例如,globalShortAveragePrice变量)。根据orderbook-executionrouter-execution路径,在执行用户订单之前调用ShortTracker合约的updateGlobalShortData()函数,以同步最新的全局空头数据。此步骤可确保用户仓位的正确盈亏计算。

减少WETH仓位

与其它订单不同,通过orderbook-execution路径减少WETH仓位将调用OrderBook合约中的_transferOutETH()函数来提取WETH代币并将原生ETH代币转账给用户。如果接收者_receiver是一个合约,sendValue()可能会触发合约的fallback()函数,引入潜在的重入漏洞。在此次事件中,攻击者反复利用此重入漏洞提取了巨额利润。

GLP Token

GLP(GMX流动性提供者)代币代表了Vault合约中各种资产的统一份额。用户可以通过铸造和销毁GLP来提供和赎回资产。

GLP价格的计算方式如下:

GLP价格=AUMGLPTotalSupply\text{GLP价格}= \frac{\text{AUM}}{\text{GLPTotalSupply}}

AUM=i=asset[0]assets(ΔGlobalShort[i]+AUMOther[i])\text{AUM} = \sum^{assets}_{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} }

其中:

  • GLPTotalSupply代表GLP代币的总供应量。
  • AUM由以下两部分组成。
    • ΔGlobalShort\Delta_{\text{GlobalShort}}代表所有空头仓位的未实现盈亏(uPnL)。
    • AUMOther\text{AUM}_{\text{Other}}代表LP提供的流动性加上所有多头仓位的未实现uPnL。在利润实现之前,这一项在事件期间几乎保持不变。
  • AssetMarketPrice是底层资产的市场价格(以美元计)。
  • globalShortSize是所有空头仓位的总和(以美元计)。
  • globalShortAveragePrice是聚合的全局空头仓位的平均入场价格。

在此次事件中,攻击者通过扭曲globalShortAveragePrice来操纵GLP价格并提取利润。

漏洞分析

根本原因是OrderBook合约中的跨合约重入漏洞。如背景所述,通过orderbook-execution路径减少WETH仓位会触发_transferOutETH()向接收者进行低级回调。该函数带有nonReentrant修饰符,但此保护仅阻止OrderBook合约内部的重入,而不能阻止对Vault合约的跨合约调用。

在正常操作下,VaultincreasePosition()只能通过PositionRouterPositionManager调用,它们会在每次仓位变动前调用ShortTracker来更新globalShortAveragePrice。攻击者创建了以恶意合约作为接收者的订单,并在回调期间,直接调用Vault上的increasePosition(),绕过了PositionRouter/PositionManager以及相关的ShortTracker更新。通过反复开平空头仓位而不更新globalShortAveragePrice,攻击者逐步扭曲了该变量。扭曲的值放大了GLP价格,从而使攻击者可以通过铸造和赎回GLP来提取利润。

攻击分析

攻击者发起了一系列交易,操纵全局空头数据并实现利润。具体来说,此次事件可以分为三个阶段:准备价格操纵利润实现。所有相关交易如下表所示。

交易号 阶段 描述 交易 时间 (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) Keeper执行了攻击者(步骤2中创建)的增加订单,从而为攻击合约开立了一个WETH多头仓位。(Tx 3)。

  4. (Tx 4) 攻击者在OrderBook合约中为攻击合约创建了一个减少WETH多头订单。

操纵阶段

  1. (Tx 5) Keeper通过orderbook-execution路径执行了攻击合约(步骤4中创建)的减少WETH多头订单。执行路径调用了_transferOutETH(),从而触发了攻击合约中的恶意fallback()函数。

    由于fallback()是在“杠杆窗口”期间被调用的,攻击合约直接与Vault合约交互,通过increasePosition()开立了一个WBTC空头仓位,而没有更新globalShortAveragePrice,随后又创建了一个减少/平仓WBTC空头订单。

  2. (Tx 6) Keeper通过router-execution路径执行了(步骤5中创建的)减少WBTC空头订单。执行过程还通过PositionRouter合约中的回退机制创建了一个减少WETH多头订单。

    由于WBTC空头仓位是在未更新globalShortAveragePrice的情况下开立的,在更新该变量的同时平仓导致globalShortAveragePrice意外下降。

  3. (Tx 7-14) 攻击者重复了步骤5-6四次。结果,globalShortAveragePrice1.08e35扭曲到了1.9e33

利润实现阶段

  1. (Tx 15) Keeper通过orderbook-execution路径执行了(Tx 14中创建的)减少WETH多头订单。此执行由于OrderBook合约中的重入漏洞,触发了攻击合约中的利润实现逻辑。

    1. 在回退调用中,攻击合约首先通过闪电贷借入了7,538,567e18 USDC。

    2. 攻击合约调用mintAndStakeGlp(),使用6,000,000e18 USDC铸造了4,129,578e18 GLP。

    3. 攻击合约调用Vault.increasePosition(),用剩余的1,538,567e18 USDC开立了一个WBTC空头仓位。由于WBTC的全局空头数据被极度扭曲,订单执行极大地放大了AUM,从而急剧提高了GLP价格。

    4. 攻击合约调用unstakeAndRedeemGlp(),以放大的价格在Vault合约中赎回了多种资产的GLP。

    5. 攻击合约调用Vault.decreasePosition()平仓了WBTC空头仓位。

    6. 攻击合约重复了步骤8.b-8.e四次,耗尽了Vault合约中的所有资产。

    7. 攻击合约偿还了闪电贷,并获得了近4200万美元的利润。

退款

通过与攻击者协商(Tx 16-18),攻击者最终接受了10%的赏金,并退还了剩余的被盗资产(Tx 19-21)。

总结

此次事件涉及对Arbitrum上GMX V1的多阶段攻击,估计损失达4200万美元。攻击者利用了OrderBook合约中的重入漏洞来扭曲globalShortAveragePrice变量,从而放大了GLP价格。通过利用被操纵的价格,攻击者窃取了大量资产。

关键教训:

  • 跨合约重入:单个合约上的nonReentrant修饰符并不能阻止同一系统内其他合约的重入。依赖于临时标志(例如“杠杆窗口”)的访问控制机制,在标志被重置之前发生外部调用时可能会被绕过。
  • 运营安全:此攻击者分三个阶段执行了攻击,总共进行了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