October 5, 2026

The Bitget Hack: A $387.5M Forged Withdrawal Attack

How a zero-day in a third-party security product led to forged withdrawal commands that Bitget's own wallet system signed, without the attacker needing a private key.

On September 24, 2026, between 18:31 and 21:23 UTC, Bitget's hot and warm wallets sent about $387.5 million to an attacker. The money left 12 wallet addresses on 11 chains. The attacker never needed a private key. Bitget's own wallet system signed the transfers. A tool built around Bitget's withdrawal logic fed it forged withdrawal requests, and the wallet system accepted them as legitimate.

The transfers took under three hours. The first sign of the attacker in Bitget's logs came more than three weeks earlier. SlowMist's interim report, published on September 30, dates it to August 31, when a zero-day in a third-party security product gave the attacker a way in.

Bitget's reconciliation system caught the gap at 19:05 UTC, within seven minutes of the first large transfer. It did not stop the signer. Transfers to the attacker continued for another 2 hours and 18 minutes, and the signing services were shut down at 21:44.

How much did Bitget lose? $351.6M, $386.8M and $387.5M

Bitget's first notice, posted at 21:39 UTC on September 24, put the loss at approximately $351.6 million. The next day Bitget raised it to about $387.5 million. The revision added "affected assets on Zcash and TRON that were not included in the initial estimate," and Bitget was explicit that it "does not reflect further unauthorized transfers."

Bitquery traced 23 transfers worth $386.8 million at prices in the hour of each transfer. Its table has no transfers of ALGO, TIA or ATOM, all three of which Bitget lists among the 13 affected assets. This article uses $387.5 million for the reported loss.

Bitget hack timeline

August 31, 2026

SlowMist dates the first malicious activity it could find in Bitget's logs to August 31. That day a zero-day in a third-party security product, named only as Product A in the report, was used against a service running on one of the product's nodes. The attacker ran a hidden script there, pulled a database password out of an environment variable and used it to connect to the database.

Two more nodes show the same activity on September 23 and 25.

September 24, 16:07 UTC

About two and a half hours before the first transfer, the attacker logged in to the management platform of a second security product, Product B, under the identity of a Bitget employee. By SlowMist's account, the attacker then attempted command injection, changes to server configuration and uploads of malicious files.

The attacker had privileged access to two third-party security appliances, A and B, and put a web shell on appliance B with a command-and-control connection. From there the attacker moved to Bitget's production wallet job server and deployed malicious packages on it.

17:49 UTC

The malicious activity reached Bitget's wallet infrastructure 42 minutes before the first on-chain transfer. At 17:49 a withdrawal tool built around Bitget's withdrawal logic began running. SlowMist, which later recovered it from deleted files, found that it supplied fake risk-control parameters and its own withdrawal requests, then started Bitget's withdrawal process with them.

18:31 UTC

The first two transfers were tests: 93 TRX from a Tron hot wallet and 0.84 ETH from an Ethereum hot wallet. Bitget’s first notice says its security systems detected unauthorized transfers at 18:31. In a livestream on September 28, Bitget said both tests stayed under the risk-control threshold and raised no alert. The automatic block on withdrawals came 34 minutes later.

18:58 to 19:16 UTC

Just under half an hour later the real transfers began. At 18:58, 34.75 million USDT went out on Ethereum. Hypernative's reconstruction shows hot wallets on five networks sending $87.6 million within 15 seconds at 19:01. About 17,200 ZEC followed at 19:03.

At 19:05 Bitget's reconciliation system caught the discrepancy, and the risk-control system blocked withdrawal requests across the platform. That block covered withdrawals started by users, and the attacker's requests were not among them. At 19:16, warm wallets on five networks sent another $202.8 million in nine seconds.

19:40 to 21:44 UTC

Containment began at 19:40. By Bitget's count in the same livestream, the first wave of 17 large transfers, worth about $360 million, had begun at 18:58 and ran until 20:09. At 20:40, with key compromise not yet ruled out, the wallet team started moving funds to cold wallets. The second wave, seven transfers worth about $28 million, ran from 20:55 to 21:23, when 223.2 ETH left an Ethereum hot wallet. Bitget shut down its signing services at 21:44, just under four hours after the withdrawal tool first ran.

During and after the transfers, the attacker tried to clean up and to take more. SlowMist found attempts to edit the wallet database's withdrawal records. It also found two forged BTC withdrawal orders that returned errors, which the attacker then checked on and retried.

Valid signatures over unapproved transfers

By Bitget's account, forged commands were written straight into the wallet system, which executed them as normal withdrawals and skipped the risk checks that run before a withdrawal record exists. After each transfer, the attacker deleted what the forged commands had left behind.

Bitget observed no evidence that any private key was compromised, and its cold wallets were unaffected. Every stolen asset left a Bitget hot or warm wallet under a valid signature from Bitget's signing system.

A signature proves which key signed a transaction. It says nothing about who approved the payment. Hypernative, which reconstructed the transfers from the chain, wrote that "every signature was valid, and nothing checked whether what was being authorised should have been."

The transactions also show that the attacker's requests differed from the normal customer withdrawal pattern. Bitquery counted 35,678 transactions from the two Ethereum hot wallets in the eight days to September 24. Customer ETH payouts nearly all went out with one gas limit, about 63,000, while the limit on token payouts was estimated each time. Only seven of those transactions carried a limit of 100,000 or 200,000, and all seven went to the attacker.

Bitquery also notes that "Bitget's warm wallet uses fixed limits like these for its own internal transfers," so the round numbers set the theft apart from customer withdrawals, while Bitget's own internal transfers carry the same limits. None of that mattered to the signer, which in Hypernative's summary of Bitget's account "trusted an internal service to tell it the destination and amount."

Reconciliation at 19:05, attacker transfers until 21:23

Reconciliation compares an exchange's internal records with what its wallets hold. At 19:05 Bitget's reconciliation system found "a significant discrepancy." A reconciliation check runs after transfers happen, so it can detect a theft but cannot refuse the signature behind it.

The 19:16 burst of $202.8 million went out 11 minutes after the alert. About $149 million had left before the alert, and about $238 million, more than 60% of the total, left after it.

On-chain analysis showed the unauthorized transfers "were executed alongside legitimate internal wallet operations on Ethereum" until about 21:23 UTC. The record of Bitget 6, the hot wallet behind the 0.84 ETH test and the 7,130.86 ETH transfer, shows what that looked like.

For an externally owned account such as this wallet, the nonce counts the transactions sent from it. In the 30 minutes between the test at 18:31:11 and the 7,130.86 ETH transfer at 19:01:35, Bitget 6's nonce rose by 67. In the 68 minutes after that, up to the attacker's 1,879.2 ETH at 20:09:11, it rose by 29. The wallet's ordinary traffic had fallen to a fraction of its earlier rate, which is what a block on user withdrawals would produce. The attacker's transfers continued.

At 20:41:59, two minutes after the wallet team began moving funds to cold wallets, Bitget 6 sent more than 70 token transfers in a single Ethereum block, each with its own computed gas limit. Bitquery, which tracked the same movement on Ethereum and BNB Chain, wrote that it "looks like an emergency sweep." Forty-one minutes later the same wallet sent 223.2 ETH to the attacker. For that last stretch, Bitget's own operations and the attacker's forged requests went out of the same wallet, in one nonce sequence.

Where the stolen Bitget funds went and how much was frozen

The attacker's first move was out of the assets an issuer could freeze. The stablecoins and the tokenized gold sold on Uniswap in batches, through an account set up with EIP-7702 so that one call could carry many swaps. Twenty-three minutes after the first USDT arrived there, 22,320 ETH left it.

XRP was the largest item, at $157.8 million. It is the XRP Ledger's native asset, and no issuer can freeze it. The attacker's XRP accounts were empty by September 27, with 90.5% of the stolen XRP swapped for Bitcoin, and 29,088 ETH had been converted to Bitcoin through THORChain by September 29.

The ZEC went somewhere harder to follow. ZachXBT flagged the first deposits into Zcash's new Ironwood shielded pool on September 30, and by midday UTC Bitquery counted 3,259 ZEC inside it, out of reach of on-chain tracing. BlockSec put the attacker's remaining holdings at about $342 million as of September 29, or 88.3% of what was stolen, most of it already in Bitcoin.

The freezes are small. Circle and Tether froze about $339,000 of USDC and USDT, and BlockSec found that these were amounts the attacker had left behind, such as cross-chain transfers that arrived more than an hour late. NEAR Intents froze about $503,000 of the more than $50 million the attacker tried to move through it, according to its general manager.

Bitget's CEO, Gracy Chen, told CNBC that about $1.1 million had been frozen in total and that she was "not expecting to recover a lot of funds," Quartz reported on October 2. That is less than 0.3% of the loss. THORChain declined Bitget's request to refuse service to the published attacker addresses.

Was North Korea behind the Bitget hack?

Bitget pointed to North Korea within hours. Its first indicators were IP addresses that matched VPN services used by a North Korean group, and a method its CEO described as "highly consistent with known patterns of North Korean hacker organizations," based on "IP behavior patterns and on-chain analysis."

Two analytics firms published evidence from the laundering. TRM Labs reported "multiple overlaps with wallets used to launder previous North Korean hacks, including Bybit and AFX Bridge." Elliptic called a link to North Korea "highly likely," and noted that "the exchange's own technical basis for that attribution has not yet been published."

The evidence is of two kinds, and they support different claims. IP and VPN indicators, which Bitget has described but not published, concern the intruder. Laundering overlaps concern the people moving the money. TRM bridges the two with an inference. The firm wrote that it "has not linked this laundering syndicate to any other hacking group's thefts," and that "these overlaps therefore point to TraderTraitor."

Neither interim report attributes the attack to anyone. Bitget's CEO later told Bloomberg that the early indicators "should not be treated as a definitive attribution," in a report published on September 29. Chainalysis, on October 1, called the thieves "DPRK-attributed threat actors" without giving its basis. As of October 2, no government had formally blamed North Korea for the theft.

How the Bitget hack compares with Bybit and BigONE

Two earlier exchange thefts also turned on what the signer was given to sign.

In February 2025, Bybit lost about $1.5 billion from an Ethereum cold wallet. Sygnia found that JavaScript served from the S3 bucket behind the SafeWallet interface had been modified two days before the theft, and that the injected code's "primary objective was to modify transaction content with hardcoded parameters." The FBI attributed the theft to North Korea five days after it happened.

In July 2025, BigONE lost more than $27 million from a hot wallet. SlowMist reported that "the operating logic of account and risk control-related servers was modified, enabling the attacker to withdraw funds," and that "the private keys were not leaked."

In both cases the signing layer produced a valid signature over a transfer its operators did not intend. At Bybit a transaction that employees meant to make was altered before it was signed. At BigONE and Bitget there was no such transaction, and the requests themselves were the attacker's.

What formal verification can establish about withdrawal signing

Two security properties emerge from this incident.

The first is binding, which means that each transfer a wallet system signs corresponds to one approved withdrawal record, or one treasury allowlist entry, for the same chain, asset, destination and amount. This is the kind of property formal verification can express and check across every execution path, including paths no test case exercises. Signing consumes that approval, so it cannot be used twice. By Bitget's account, the transfers to the attacker started instead from forged commands placed directly in the wallet system.

The second is independent approval. Binding only helps if the approval comes from somewhere the requester cannot write to. The attacker's tool forged the risk-control parameters themselves. A check that compares a request with its approval passes when the attacker wrote both.

Formal verification can establish that the wallet enforces the binding rule, but it cannot by itself establish that the approval originates from a trusted and independent process. That is a separate security property that has to be designed into the system.

Bitget's list of fixes points the same way. The exchange says that "multiple approvals are now required for critical operations" and, on its incident page, that it has "strengthened independent withdrawal verification."

Reconciliation checks neither property. It compares totals, so the books can balance while money goes to the wrong recipient.

Neither property stops the intrusion itself. A zero-day in a security product and a misused employee identity are outside anything a signing policy can see.

Current status

Bitget reopened withdrawals in phases: BTC on September 28, ETH on September 29 and USDT on September 30. The remaining tokens, fiat and P2P services followed on October 2, when Bitget said the plan was complete. Bloomberg reported roughly $463 million of net outflows in the 24 hours to September 29, "the largest one-day net outflow since data aggregator DefiLlama began tracking proof-of-reserves four years ago."

The User Protection Fund held more than $464 million when the incident began and fell below $200 million after Bitget began using it to absorb the financial impact. On September 30, Bitget replenished it to more than $300 million. Its latest incident update, published October 4, puts the fund at more than $314 million and the total reserve ratio at 127%.

The interim reports leave pieces missing. Neither names the two security products or their vendors, and neither explains how the attacker obtained the employee identity used on Product B. Both investigations were still open when Bitget published the reports on September 30. Bitget has said a formal security report with more technical detail will follow, and as of October 2 it had not been published. That report should also show how the signer now decides what to sign.

--

CERTORA

Try Certora Prover, the free formal verification tool.

See what AutoProver automates, AI-assisted specification generation built on top of Prover.

Get a Certora smart contract audit, manual review combined with formal methods.

Explore Certora's formal verification capabilities.

Provable is a Certora production.

Get every blog post delivered

Certora Logo
logologo
Terms of UsePrivacy Policy