All incidents

HegicPUT Repeat Withdrawal Drain

Share
Jan 24, 2025 01:38 UTCAttackLoss: 1.08 WBTCPending manual check5 exploit txWindow: 30d 22h
Estimated Impact
1.08 WBTC
Label
Attack
Exploit Tx
5
Addresses
1
Attack Window
30d 22h
Jan 24, 2025 01:38 UTC → Feb 23, 2025 23:51 UTC

Exploit Transactions

TX 1Ethereum
0x3fdffdd066a19a9658f014c68fb9d7a4385d96526477335dbb05492540266add
Jan 24, 2025 01:38 UTCExplorer
TX 2Ethereum
0x9c27d45c1daa943ce0b92a70ba5efa6ab34409b14b568146d2853c1ddaf14f82
Jan 24, 2025 01:39 UTCExplorer
TX 3Ethereum
0x260d5eb9151c565efda80466de2e7eee9c6bd4973d54ff68c8e045a26f62ea73
Feb 23, 2025 23:48 UTCExplorer
TX 4Ethereum
0x722f67f6f9536fa6bbf4af447250e84b8b9270b66195059c9904a0e249543e80
Feb 23, 2025 23:49 UTCExplorer
TX 5Ethereum
0x444854ee7e7570f146b64aa8a557ede82f326232e793873f0bbd04275fa7e54c
Feb 23, 2025 23:51 UTCExplorer

Victim Addresses

0x7094e706e75e13d1e0ea237f71a7c4511e9d270bEthereum

Loss Breakdown

1.08WBTC

Similar Incidents

Root Cause Analysis

HegicPUT Repeat Withdrawal Drain

1. Incident Overview TL;DR

An unprivileged Ethereum account deployed a helper contract, acquired 250000 satoshis of WBTC, deposited that WBTC into HegicPUT at 0x7094e706e75e13d1e0ea237f71a7c4511e9d270b, and minted tranche NFT 2. After the public 30 day lockup elapsed, the same tranche was redeemed repeatedly, first 100 times in tx 0x260d5eb9151c565efda80466de2e7eee9c6bd4973d54ff68c8e045a26f62ea73 and then 331 times in tx 0x444854ee7e7570f146b64aa8a557ede82f326232e793873f0bbd04275fa7e54c. Each redemption transferred another 250000 satoshis of WBTC from the pool. The root cause is a missing open-state check in HegicPool._withdraw, so a matured tranche remains redeemable after its first withdrawal.

2. Key Background

HegicPUT is an ERC721-based pool where each liquidity provision mints a tranche NFT that should represent a one-time redemption claim after the pool lockup expires. The incident used public interfaces only: Uniswap V2 for funding, WBTC approval, and HegicPUT for deposit and withdrawal. The attacker helper contract at 0xf51e888616a123875eaf7afd4417fbc4111750f7 was only an owner-gated wrapper around those public calls. The exploitability therefore does not depend on attacker secrets or privileged integrations; any user controlling a matured tranche can trigger the same repeated-withdraw behavior.

3. Vulnerability Analysis & Root Cause Summary

The vulnerability is a broken single-use redemption invariant in HegicPool._withdraw. The intended safety property is that once a tranche is withdrawn, it becomes closed and cannot transfer funds again. In the deployed HegicPool source, the guard require(t.state == TrancheState.Open); is commented out, while the rest of the withdrawal logic still executes. The function sets t.state = TrancheState.Closed, but that state change has no enforcement effect on later calls. It then recomputes amount = (t.share * totalBalance) / totalShare from the original stored t.share, reduces global share totals, and transfers WBTC to the owner each time. That makes a closed tranche repeatedly withdrawable as long as the pool still has positive balance and share accounting remains nonzero.

4. Detailed Root Cause Analysis

The critical code path is the verified HegicPool._withdraw implementation:

function _withdraw(address owner, uint256 trancheID)
    internal
    returns (uint256 amount)
{
    Tranche storage t = tranches[trancheID];
    // require(t.state == TrancheState.Open);
    require(_isApprovedOrOwner(_msgSender(), trancheID));
    require(block.timestamp > t.creationTimestamp + lockupPeriod, "Pool Error: The withdrawal is locked up");

    t.state = TrancheState.Closed;
    amount = (t.share * totalBalance) / totalShare;
    totalShare -= t.share;
    totalBalance -= amount;

    token.safeTransfer(owner, amount);
}

The missing open-state guard is sufficient to explain the incident end to end. First, tx 0x9c27d45c1daa943ce0b92a70ba5efa6ab34409b14b568146d2853c1ddaf14f82 transferred exactly 250000 satoshis of WBTC into HegicPUT and minted tranche NFT 2 to the helper contract. After the 30 day lockup, the attacker called the helper, which repeatedly invoked withdrawWithoutHedge(2). Receipt 0x260d5e... contains 100 WBTC transfers of 0x3d090 (250000) from HegicPUT to the helper and 100 matching Hegic Withdrawn logs for tranche 2. Receipt 0x444854... contains 331 more matching pairs. Those receipts demonstrate that the same tranche remained callable after closure and that each call emitted a fresh payout, exactly matching the faulty source logic above.

5. Adversary Flow Analysis

The adversary flow is deterministic and fully on-chain.

1. Deploy helper contract from EOA 0x4b53608ff0ce42cdf9cf01d7d024c2c9ea1aa2e8
2. Swap ETH for 250000 satoshis of WBTC on Uniswap V2
3. Approve HegicPUT and call provideFrom(...) to mint tranche NFT 2
4. Wait until block.timestamp exceeds creationTimestamp + lockupPeriod
5. Repeatedly call withdrawWithoutHedge(2) through the helper
6. Sweep drained WBTC from helper to attacker EOA

The helper runtime disassembly shows dedicated entrypoints consistent with ownership checks, swap logic, repeated withdrawal orchestration, token sweep, and ERC721 receipt handling. Receipt 0x722f67f6f9536fa6bbf4af447250e84b8b9270b66195059c9904a0e249543e80 then records a WBTC sweep from the helper to the controlling EOA, proving profit realization. The attacker contract is therefore incidental execution infrastructure, not the protocol failure. The exploit predicate is realized once the same matured tranche can produce more than one positive payout.

6. Impact & Losses

The measured loss in the documented exploit transactions is 107750000 satoshis of WBTC, or 1.0775 WBTC at 8 decimals. The affected party is HegicPUT, which transferred WBTC out of pool reserves for the same tranche NFT hundreds of times. The first repeated-withdraw transaction drained 100 * 250000 = 25000000 satoshis, and the second drained 331 * 250000 = 82750000 satoshis. Because the logic flaw is permissionless and tied to tranche ownership rather than attacker-specific state, the vulnerability exposes any sufficiently funded pool state to the same class of drain.

7. References

  • Verified victim source: HegicPool.sol for 0x7094e706e75e13d1e0ea237f71a7c4511e9d270b
  • Helper deployment metadata and runtime disassembly for 0xf51e888616a123875eaf7afd4417fbc4111750f7
  • Tranche mint transaction: 0x9c27d45c1daa943ce0b92a70ba5efa6ab34409b14b568146d2853c1ddaf14f82
  • First repeated-withdraw transaction: 0x260d5eb9151c565efda80466de2e7eee9c6bd4973d54ff68c8e045a26f62ea73
  • Helper-to-EOA sweep transaction: 0x722f67f6f9536fa6bbf4af447250e84b8b9270b66195059c9904a0e249543e80
  • Second repeated-withdraw transaction: 0x444854ee7e7570f146b64aa8a557ede82f326232e793873f0bbd04275fa7e54c