2025年7月9日,去中心化永续合约平台GMX [1, 2] 上部署在Arbitrum网络的V1合约遭到攻击,导致约4200万美元的损失。攻击者利用了跨合约重入漏洞操纵GLP价格,并利用扭曲的价格从GMX V1的流动性池中窃取底层资产。
背景
GMX V1 [3] 是一个部署在Arbitrum上的去中心化永续合约交易平台。它允许用户以无需许可和非托管的方式,交易多资产的杠杆永续合约。GMX V1遵循单一多资产池设计,所有支持资产的流动性聚合到一个统一的Vault系统中。
GMX中的仓位管理
GMX中的仓位管理是一个两步过程。具体来说,用户通过与OrderBook或PositionRouter合约交互来创建增加/减少订单。然后,授权的keeper账户执行用户的订单,这将更新ShortTracker合约中的全局空头数据以及Vault合约的相应状态。以下两图展示了在GMX中管理仓位的两个执行路径(orderbook-execution和router-execution路径)。在此次事件中,攻击者同时利用了这两个路径来操纵全局空头数据并实现利润。
Orderbook-execution 路径
Router-execution 路径
GMX Vault
GMX中的Vault合约负责管理用户的仓位和资产(例如,最终确定盈亏)。为了防止恶意交互,Vault合约仅在“杠杆窗口”打开时(即通过Timelock.enableLeverage()函数将Vault合约中的isLeverageEnabled变量设置为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 Token
GLP(GMX流动性提供者)代币代表了Vault合约中各种资产的统一份额。用户可以通过铸造和销毁GLP来提供和赎回资产。
GLP价格的计算方式如下:
其中:
GLPTotalSupply代表GLP代币的总供应量。AUM由以下两部分组成。- 代表所有空头仓位的未实现盈亏(uPnL)。
- 代表LP提供的流动性加上所有多头仓位的未实现uPnL。在利润实现之前,这一项在事件期间几乎保持不变。
AssetMarketPrice是底层资产的市场价格(以美元计)。globalShortSize是所有空头仓位的总和(以美元计)。globalShortAveragePrice是聚合的全局空头仓位的平均入场价格。
在此次事件中,攻击者通过扭曲globalShortAveragePrice来操纵GLP价格并提取利润。
漏洞分析
根本原因是OrderBook合约中的跨合约重入漏洞。如背景所述,通过orderbook-execution路径减少WETH仓位会触发_transferOutETH()向接收者进行低级回调。该函数带有nonReentrant修饰符,但此保护仅阻止OrderBook合约内部的重入,而不能阻止对Vault合约的跨合约调用。
在正常操作下,Vault的increasePosition()只能通过PositionRouter和PositionManager调用,它们会在每次仓位变动前调用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 |
准备阶段
-
(Tx 1) 攻击者部署了攻击合约,该合约在订单执行期间被用作资产接收者。攻击合约包含一个恶意的
fallback()函数。 -
(Tx 2) 攻击者在
OrderBook合约中为攻击合约创建了一个增加WETH多头订单。
-
(Tx 3) Keeper执行了攻击者(步骤2中创建)的增加订单,从而为攻击合约开立了一个WETH多头仓位。(Tx 3)。
-
(Tx 4) 攻击者在
OrderBook合约中为攻击合约创建了一个减少WETH多头订单。
操纵阶段
-
(Tx 5) Keeper通过orderbook-execution路径执行了攻击合约(步骤4中创建)的减少WETH多头订单。执行路径调用了
_transferOutETH(),从而触发了攻击合约中的恶意fallback()函数。由于
fallback()是在“杠杆窗口”期间被调用的,攻击合约直接与Vault合约交互,通过increasePosition()开立了一个WBTC空头仓位,而没有更新globalShortAveragePrice,随后又创建了一个减少/平仓WBTC空头订单。
-
(Tx 6) Keeper通过router-execution路径执行了(步骤5中创建的)减少WBTC空头订单。执行过程还通过
PositionRouter合约中的回退机制创建了一个减少WETH多头订单。由于WBTC空头仓位是在未更新
globalShortAveragePrice的情况下开立的,在更新该变量的同时平仓导致globalShortAveragePrice意外下降。
-
(Tx 7-14) 攻击者重复了步骤5-6四次。结果,
globalShortAveragePrice从1.08e35扭曲到了1.9e33。
利润实现阶段
-
(Tx 15) Keeper通过orderbook-execution路径执行了(Tx 14中创建的)减少WETH多头订单。此执行由于
OrderBook合约中的重入漏洞,触发了攻击合约中的利润实现逻辑。
-
在回退调用中,攻击合约首先通过闪电贷借入了7,538,567e18 USDC。
-
攻击合约调用
mintAndStakeGlp(),使用6,000,000e18 USDC铸造了4,129,578e18 GLP。 -
攻击合约调用
Vault.increasePosition(),用剩余的1,538,567e18 USDC开立了一个WBTC空头仓位。由于WBTC的全局空头数据被极度扭曲,订单执行极大地放大了AUM,从而急剧提高了GLP价格。 -
攻击合约调用
unstakeAndRedeemGlp(),以放大的价格在Vault合约中赎回了多种资产的GLP。 -
攻击合约调用
Vault.decreasePosition()平仓了WBTC空头仓位。 -
攻击合约重复了步骤8.b-8.e四次,耗尽了
Vault合约中的所有资产。 -
攻击合约偿还了闪电贷,并获得了近4200万美元的利润。
-
退款
通过与攻击者协商(Tx 16-18),攻击者最终接受了10%的赏金,并退还了剩余的被盗资产(Tx 19-21)。
总结
此次事件涉及对Arbitrum上GMX V1的多阶段攻击,估计损失达4200万美元。攻击者利用了OrderBook合约中的重入漏洞来扭曲globalShortAveragePrice变量,从而放大了GLP价格。通过利用被操纵的价格,攻击者窃取了大量资产。
关键教训:
- 跨合约重入:单个合约上的
nonReentrant修饰符并不能阻止同一系统内其他合约的重入。依赖于临时标志(例如“杠杆窗口”)的访问控制机制,在标志被重置之前发生外部调用时可能会被绕过。 - 运营安全:此攻击者分三个阶段执行了攻击,总共进行了15笔交易。此次事件凸显了实时监控、及时警报和有效缓解预案的至关重要性。
参考
- https://x.com/GMX_IO/status/1942955807756165574
- https://x.com/GMX_IO/status/1943336664102756471
- GMX V1
关于BlockSec
BlockSec是一家提供全栈式区块链安全和加密合规服务的提供商。我们构建的产品和服务,在协议和平台的整个生命周期内,帮助客户进行代码审计(包括智能合约、区块链和钱包)、实时拦截攻击、分析事件、追踪非法资金,并满足AML/CFT要求。
BlockSec在知名会议上发表了多篇区块链安全论文,报告了多个DeFi应用的零日攻击,阻止了多次黑客攻击并挽救了超过2000万美元的资产,并确保了数十亿美元加密货币的安全。
-
官方Twitter账号:https://twitter.com/BlockSecTeam
-
🔗 BlockSec审计服务 : 提交请求
-
🔗 Phalcon安全APP: 预约演示



