GMX攻击分析&基于Wake的攻击场景复现

  • Ackee
  • 发布于 2025-07-23 18:15
  • 阅读 19

该分析详细介绍了GMX协议遭受的4200万美元攻击事件,攻击源于跨合约重入漏洞,该漏洞绕过了增加仓位时的访问控制。攻击者利用此漏洞操纵GLP代币价格,使其价格升高,从而以操纵后的价格赎回代币,并从协议中提取利润。文章还提供了一个可重现攻击场景的工作示例,用于教育目的。

这个分析检查了针对 GMX 协议的 42M 攻击。 我们提供了漏洞的详细技术分析,并包括一个可在分叉环境中重现攻击场景的工作示例,以用于教育目的。

该攻击利用了 跨合约重入漏洞,该漏洞绕过了增加仓位期间的访问控制。这导致 GLP 代币价格被人为抬高,允许攻击者以被操纵的价格赎回代币,并从协议中提取利润。

wake 测试输出

使用 Wake 重现

  1. 克隆 存储库
  2. 导入 GMX 项目依赖项:
$ npm i
  1. 初始化 Wake:
$ wake up
  1. 从 Alchemy 或其他提供商处获取 Arbitrum 分叉 URL,并在 .env 中设置它,类似于 .env.example
  2. 运行测试:
$ wake test tests/test_attack_simple.py
  1. 取消注释 print(tx.call_trace) 以查看调用跟踪。

根本原因

该漏洞源于重入问题。虽然重入本身很简单,但其影响是巨大的。

核心问题:GLP 代币价格计算依赖于 ShortsTracker 中的 globalShortAveragePrices 变量。这种依赖性创建了一个可利用的攻击向量。

该漏洞是跨合约重入。交易期间涉及多个合约。每个合约都有一个重入保护;但是,重入发生在某个特定合约已经退出之后。

有关跨合约重入的详细解释,请参见 本综合指南

入口点

当用户增加他们的仓位时,攻击开始:

  1. 用户调用 createIncreaseOrder 来注册订单
  2. orderkeeper 机器人调用 PositionManager.executeIncreaseOrder 来执行它
  3. executeIncreaseOrder 中,调用 ShortsTracker.updateGlobalShortData

ShortsTracker.updateGlobalShortData 存储 token 的 globalShortAveragePrice——所有空头头寸的平均入场价格。此值直接影响 GLP 代币价格的计算。

contract PositionManager {
    function executeIncreaseOrder(
        address _account,
        uint256 _orderIndex,
        address payable _feeReceiver
    ) external onlyOrderKeeper {
        //...
        IShortsTracker(shortsTracker).updateGlobalShortData(_account, collateralToken, indexToken, isLong, sizeDelta, markPrice, true);
        ITimelock(timelock).enableLeverage(_vault); // isLeverageEnabled <- True
        IOrderBook(orderBook).executeIncreaseOrder(_account, _orderIndex, _feeReceiver);
        ITimelock(timelock).disableLeverage(_vault); // isLeverageEnabled <- False
        _emitDecreasePositionReferral(_account, sizeDelta);
    }
}

外部调用遵循此路径到达 Vault:

  • OrderBook.executeIncreaseOrder
    • Router.pluginIncreasePosition
    • Vault.increasePosition

decreasePosition 流程遵循类似的模式。

Vault.increasePosition 函数检查 isLeverageEnabled 是否等于 True 以验证调用发生在 Timelock.enableLeverageTimelock.disableLeverage 之间。 事实证明,此检查是不够的。

contract Vault {
    // function has no msg.sender check.
    // Assumes caller transfers tokens or at least the caller is trusted.
    function increasePosition(
        address _account,
        address _collateralToken,
        address _indexToken,
        uint256 _sizeDelta,
        bool _isLong
    ) external override nonReentrant {
        _validate(isLeverageEnabled, 28); // this will be bypassed
        _validateGasPrice();
        _validateRouter(_account);
        ...
        ...
    }
}

Vault.decreasePosition 期间,合约转移已平仓头寸的抵押代币。当抵押代币是 WETH 时,系统会提取 ETH 并将其转移到用户的帐户。值得注意的是,这些 WETH 操作发生在 Vault 合约之外。

呼叫流程如下:

  • OrderBook.executeDecreaseOrder

    • Router.pluginDecreasePosition
    • Vault.decreasePosition
      1. ReentrancyGuard 设置为 ENTERED
      2. Vault 平仓
      3. 将 WETH 发送到 OrderBook
      4. ReentrancyGuard 设置为 NOT_ENTERED
    • OrderBook 提取 ETH
    • 将 ETH 转移给用户
    • User.receive 被触发

      Vault.increasePosition (利用)

      1. ReentrancyGuard 确认 NOT_ENTERED
      2. ReentrancyGuard 设置为 ENTERED
      3. 攻击继续…

Vault 中的重入保护最初为 NOT_ENTERED,但重入调用发生在此状态已重置之后,绕过了保护。

攻击升级

直接的 Vault.increasePosition 调用绕过 ShortsTracker.updateGlobalShortData,导致 GlpManager.getAum 返回膨胀的值并人为地增加 GLP 代币的价格。

攻击序列:

  1. 通过开放入口点重新进入
  2. 增加流动性以获得 GLP 代币
  3. 调用 increasePosition 以向上操纵 GLP 代币价格
  4. 以虚高的 GLP 代币价格移除流动性

运营细节

攻击者使用 RewardRouterV2.mintAndStakeGlp,因为 GLPManager.inPrivateMode 已启用,从而阻止直接调用 GLPManager.addLiquidity

攻击者在 fallback 函数中使用 USDC 的闪电贷创建了大量的 WBTC 空头头寸。

总结

由于跨合约的数据责任分散,攻击得以成功。关键状态信息在 ShortsTrackerVault 之间拆分,导致重入保护无效。这种架构漏洞允许攻击者通过精心策划的重入调用来操纵 GLP 代币价格,从而实现了数百万美元的利用。

  • 原文链接: ackee.xyz/blog/gmx-hack-...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ackee
Ackee
Cybersecurity experts | We audit Ethereum and Solana | Creators of @WakeFramework , Solidity (Wake) & @TridentSolana | Educational partner of Solana Foundation