On Wednesday, 9th July 2025, GMX was exploited for approximately $42 million USD1 via a reentrancy vulnerability that enabled price manipulation of the GLP token. The attacker successfully extracted the stolen funds but kept them onchain. The entirety of the stolen funds have since been returned to GMX although a $5 million USD1 bounty was paid to the attacker. The attacker remains unidentified and at large.
Exploiter: 0xDF3340A436c27655bA62F8281565C9925C3a5221
via 0x7D3BD50336f64b7A473C51f54e7f0Bd6771cc355
Exploit Transaction: 0x03182d3f0956a91c4e4c8f225bbc7975f9434fab042228c7acdc5ec9a32626ef
(Trace)
Exploit Chain: Arbitrum (42161)
Exploited Codebase: gmx-io/gmx-contracts @ 84f636fa863563d369fe7ecd11dae6b099339cd3
Exploited Assets:
Audits: ABDK, Quantstamp, Guardian2
CVSS 3.1 Score: 9.3 (Critical)
CVSS 3.1 Vector: AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N/E:F/RL:O/RC:C/CR:X/IR:H/AR:X/MAV:N/MAC:L/MPR:N/MUI:N/MS:C/MC:N/MI:H/MA:N
SWCs: SWC-107
The initial issue is that the OrderBook
contract is vulnerable to cross-contract reentrancy. This is despite an explicit nonReentrant
guard on the affected OrderBook::executeDecreaseOrder
method, which only ensures non-reentrancy within the same contract.
The deeper issue is that when a short position is entered into, this immediately updates protocol-wide statistics used for risk calculations (specifically, Vault::globalShortAveragePrices
). This occurs in Vault::increasePosition
:
if (globalShortSizes[_indexToken] == 0) {
globalShortAveragePrices[_indexToken] = price;
} else {
globalShortAveragePrices[_indexToken] = getNextGlobalShortAveragePrice(_indexToken, price, _sizeDelta);
}
_increaseGlobalShortSize(_indexToken, _sizeDelta);
-- contracts/core/Vault.sol:618-624
This modified state is then later used in protocol-wide Assets Under Management (AUM) calculations in the following block in GlpManager
(annotations added):
function getAum(bool maximise) public view returns (uint256) {
uint256 length = vault.allWhitelistedTokensLength();
uint256 aum = aumAddition;
uint256 shortProfits = 0;
IVault _vault = vault;
for (uint256 i = 0; i < length; i++) {
address token = vault.allWhitelistedTokens(i);
bool isWhitelisted = vault.whitelistedTokens(token);
if (!isWhitelisted) {
continue;
}
uint256 price = maximise ? _vault.getMaxPrice(token) : _vault.getMinPrice(token);
uint256 poolAmount = _vault.poolAmounts(token);
uint256 decimals = _vault.tokenDecimals(token);
if (_vault.stableTokens(token)) {
aum = aum.add(poolAmount.mul(price).div(10 ** decimals));
} else {
// add global short profit / loss
uint256 size = _vault.globalShortSizes(token);
if (size > 0) {
(uint256 delta, bool hasProfit) = getGlobalShortDelta(token, price, size); // <--- tainted values finally used here
if (!hasProfit) {
// add losses from shorts
aum = aum.add(delta);
} else {
shortProfits = shortProfits.add(delta);
}
}
aum = aum.add(_vault.guaranteedUsd(token));
uint256 reservedAmount = _vault.reservedAmounts(token);
aum = aum.add(poolAmount.sub(reservedAmount).mul(price).div(10 ** decimals));
}
}
aum = shortProfits > aum ? 0 : aum.sub(shortProfits);
return aumDeduction > aum ? 0 : aum.sub(aumDeduction);
}
function getGlobalShortDelta(address _token, uint256 _price, uint256 _size) public view returns (uint256, bool) {
uint256 averagePrice = getGlobalShortAveragePrice(_token); // <----- tainted values propagated here
uint256 priceDelta = averagePrice > _price ? averagePrice.sub(_price) : _price.sub(averagePrice);
uint256 delta = _size.mul(priceDelta).div(averagePrice);
return (delta, averagePrice > _price);
}
function getGlobalShortAveragePrice(address _token) public view returns (uint256) {
IShortsTracker _shortsTracker = shortsTracker;
if (address(_shortsTracker) == address(0) || !_shortsTracker.isGlobalShortDataReady()) {
return vault.globalShortAveragePrices(_token); // <---- these values have been manipulated earlier in `Vault::increasePosition`
}
uint256 _shortsTrackerAveragePriceWeight = shortsTrackerAveragePriceWeight;
if (_shortsTrackerAveragePriceWeight == 0) {
return vault.globalShortAveragePrices(_token);
} else if (_shortsTrackerAveragePriceWeight == BASIS_POINTS_DIVISOR) {
return _shortsTracker.globalShortAveragePrices(_token);
}
uint256 vaultAveragePrice = vault.globalShortAveragePrices(_token);
uint256 shortsTrackerAveragePrice = _shortsTracker.globalShortAveragePrices(_token);
return vaultAveragePrice.mul(BASIS_POINTS_DIVISOR.sub(_shortsTrackerAveragePriceWeight))
.add(shortsTrackerAveragePrice.mul(_shortsTrackerAveragePriceWeight))
.div(BASIS_POINTS_DIVISOR);
}
-- contracts/core/GlpManager.sol:132-203
Ordinarily, Vault::increasePosition
can only be called by the permissioned GMX contracts PositionManager
and PositionRouter
; however, this reentrancy vulnerability allows the attacker to bypass these permissions checks.
During the initial OrderBook::_transferOutETH
call, GMX jumps into attacker-controlled code. This malicious contract then uses this reentrancy vulnerability to loop over many calls to Vault::increasePosition
in order to accumulate a large short position against the GLP pool. Because of the aforementioned immediate state updates, this heavily (and arbitrarily) skews the redemption price of GLP.
The actual exploit occurs in transaction 0x03182d3f0956a91c4e4c8f225bbc7975f9434fab042228c7acdc5ec9a32626ef
. The attacker reenters into GMX code via a call to OrderBook::_transferOutETH
.
During the reentrancy loop, the attacker manages to accumulate a large BTC short position. This manipulates the GMX-wide average short price for BTC from its market value of 109505.77 to 1913.70. The attacker then flashloaned a purchase of GLP at its market price of 1.45 and open a large position of notional size 15385.676. Due to the previously manipulated global average short price, this induced a calculated cumulative short loss of $\frac{15385676\left(1913.70-108757.787\right)}{1913.70}=859000107.173$. This drove the price of GLP to above $27. Finally, the attacker redeemed the minted GLP at this artificially inflated price[6].
The attacker then commences laundry of the stolen funds by converting the USDC balance into DAI via CoW Protocol (in transaction 0xce269bdfec9a489239749586fe9dcb1433eeb275aad8accdcec612224fa6eaeb
).
Time | Event |
---|---|
2025-07-07T11:40:19+00:00 | Attacker EOA is funded on Arbitrum via the Mayan Swift bridge in transaction 0xbb4188bcd0153a9572f009db6c49a07ce67e6a032f8cc1f745cef2c51fd32f62 |
2025-07-09T12:16:32+00:00 | Attacker EOA deploys attack contract 0x7D3BD50336f64b7A473C51f54e7f0Bd6771cc355 to Arbitrum in transaction 0xa4ece5a7f106f2fa62dbd0d03441183aeb650d8f587e5c1706e1e5488cd4c93f |
2025-07-09T12:30:11+00:00 | Attacker EOA invokes attack contract, completing the exploit in transaction 0x03182d3f0956a91c4e4c8f225bbc7975f9434fab042228c7acdc5ec9a32626ef |
2025-07-09T01:26:23+00:00 | Attacker EOA converts 5 million USDC to 5 million DAI on Ethereum Mainnet via CoW Protocol in transaction 0xce269bdfec9a489239749586fe9dcb1433eeb275aad8accdcec612224fa6eaeb |
2025-07-09T02:04:19+00:00 | GMX appeal to attacker onchain in transaction 0x92a39e66e54aff033cd7b41b468de7891cf459593495d68d78099cc889547380 |
2025-07-10T12:04:00+10:00 | Peckshield, Inc. report the exploit on X/Twitter |
2025-07-10T12:05:00+10:00 | BlockSec then report the actual exploit transaction on X/Twitter |
2025-07-10T12:35:00+10:00 | GMX announce on X/Twitter that an exploit has successfully occurred against their GMX v1 GLP pool |
2025-07-11T22:53:00+10:00 | GMX announce on X/Twitter that the exploit has been resolved, with the stolen funds being returned except for a $5 million USD1 bounty |
2025-07-12T02:39:00+10:00 | Rekt News announce their post-mortem analysis on X/Twitter. As part of this thread, a GMX developer claims that the entire codebase had received an additional, unpublished audit from Guardian. |
2025-07-15T15:34:00+10:00 | The author attempts to establish contact with the GMX developer for additional clarity pertaining to relevant code versions. |
[1] Rekt News, “GMX Rekt,” Rekt News. [Online]. Available: https://rekt.news/gmx-rekt. [Accessed: Jul. 15, 2025].
[2] PeckShield, “PeckShield Alert: GMX exploit,” X (formerly Twitter), Jul. 12, 2025. [Online]. Available: https://x.com/peckshield/status/1942947860645134450. [Accessed: Jul. 15, 2025].
[3] PeckShield, “Follow‑up details on GMX exploit,” X (formerly Twitter), Jul. 13, 2025. [Online]. Available: https://x.com/peckshield/status/1943460779304644615. [Accessed: Jul. 15, 2025].
[4] BlockSecTeam, “Security analysis on GMX incident,” X (formerly Twitter), Jul. 12, 2025. [Online]. Available: https://x.com/BlockSecTeam/status/1942965515007455521. [Accessed: Jul. 15, 2025].
[5] SlowMist, “SlowMist security report on GMX,” X (formerly Twitter), Jul. 12, 2025. [Online]. Available: https://x.com/SlowMist_Team/status/1942987320980054342. [Accessed: Jul. 15, 2025].
[6] GMX_IO, “Official GMX team update,” X (formerly Twitter), Jul. 14, 2025. [Online]. Available: https://x.com/GMX_IO/status/1943336664102756471. [Accessed: Jul. 15, 2025].
[7] GMX Documentation, “Security Audits,” GMX Docs. [Online]. Available: https://docs.gmx.io/docs/security/audits. [Accessed: Jul. 15, 2025].
[8] A. Manning, “Solidity Security: Comprehensive list with known pitfalls, best practices, real world attacks and potential code snippets,” Sigma Prime Blog, May 30, 2018. [Online]. Available: https://blog.sigmaprime.io/solidity-security.html. [Accessed: Jul. 15, 2025].
[9] GMX_IO, “Official GMX team update,” X (formerly Twitter), Jul. 11, 2025. [Online]. Available: https://x.com/GMX_IO/status/1943654914749534380). [Accessed: Jul. 15, 2025].
[10] xdev_10, “Official GMX team update,” X (formerly Twitter), Jul. 12, 2025. [Online]. Available: https://x.com/xdev_10/status/1943724250839580813). [Accessed: Jul. 15, 2025].
[11] BlockSecTeam, “Security analysis on GMX incident,” X (formerly Twitter), Jul. 10, 2025. [Online]. Available: https://x.com/BlockSecTeam/status/1942948251671433285. [Accessed: Jul. 15, 2025].
[12] GMX_IO, “Official GMX team update,” X (formerly Twitter), Jul. 10, 2025. [Online]. Available: https://x.com/GMX_IO/status/1942955807756165574. [Accessed: Jul. 15, 2025].
Approximate fair market value as at the time of occurrence.
This is according to one of the GMX developers on X/Twitter[10] where the claim is that the audit occurred on the entire codebase and in November 2023.