Astroport / Terra IBC Hooks Timeout-Callback Reentrancy Counterfeit-Voucher Drain (2024-07-31)

  • Name: Astroport / Terra IBC hooks timeout-callback reentrancy counterfeit-voucher drain
  • Date: 2024-07-31
  • Protocol: Terra Phoenix chain IBC hooks / Astroport migration liquidity
  • Chain: Terra + Axelar + Neutron + Ethereum
  • Category: reentrancy / timeout-callback replay / ICS-20 refund duplication / counterfeit IBC voucher mint / cross-chain drain
  • Loss: roughly $6.4M nominal at incident-time pricing across about 58M-60M ASTRO, USDC, USDT, and BTC; public chain analysis traced at least about 2.23M USDC, 350K USDT, ~2 BTC, and tens of millions of counterfeit ASTRO through the exploit path before freezes and force-transfers clawed back part of the inflated ASTRO supply
  • Summary: Terra Phoenix had reintroduced a critical April 2024 ibc-go bug in a June upgrade. The vulnerable path lived in ibc-hooks, the middleware that lets an ICS-20 token transfer memo trigger a CosmWasm callback. The bug let a malicious contract re-enter MsgTimeout from the OnTimeout hook before the packet commitment for that same transfer was deleted. In practice, one timed-out outbound transfer could be refunded repeatedly, so Terra kept minting or releasing the same IBC asset again and again even though only one packet had ever existed. The attacker first proved the path with tiny USDC refunds, then scaled it into large counterfeit USDC, USDT, and axlWBTC balances, and finally used the same primitive against Astroport’s migration-era ASTRO inventory on Terra. Those counterfeit assets were then bridged or sold out through real liquidity, turning a chain-level timeout-callback reentrancy bug into a drain of Astroport-linked migration liquidity and other bridged assets.
  • Trigger / entry point:
    • On 2024-04-05, the ibc-go team published advisory ASA-2024-007 / GHSA-j496-crgh-34mx, warning that ibc-hooks could recursively execute the same MsgTimeout before packet-commitment deletion and thereby cause unexpected escrow loss or voucher minting.
    • The issue affected chains that simultaneously used vulnerable ibc-go, enabled CosmWasm contracts, and wrapped the ICS-20 transfer application with ibc-hooks.
    • Terra had participated in the earlier patch coordination, but public reporting after the exploit said the April fix was accidentally reverted during a June chain upgrade.
    • That left Terra’s ibc-hooks timeout path live again by 2024-07-31, even though the bug class was already publicly documented.
  • Root cause:
    • Terra’s ibc-hooks middleware let a malicious CosmWasm contract receive control during the ICS-20 timeout path before the packet commitment for that timed-out transfer was deleted.
    • Because the commitment still existed, the attacker could cause the same MsgTimeout handling to execute recursively.
    • ICS-20 timeout logic is supposed to be single-use: one failed packet should cause one refund, escrow release, or voucher re-mint. Terra’s vulnerable path let that one refund right be consumed multiple times in one transaction flow.
    • Once the duplicate timeout executions minted or released counterfeit bridged assets on Terra, the rest of the ecosystem treated them as ordinary balances. They could be swapped locally, bridged out over IBC, or redeemed against migration and pool liquidity as if they were legitimate.
  • Exploit path:
    1. The attacker funded an Ethereum address 0xD5120B0857CB01CB897B57AEf4a4C4d22dD09A42, used Squid / Axelar routing to move assets toward Cosmos, and prepared Terra-side capital plus Axelar-side gas for later outbound bridging.
    2. On Terra, they instantiated a malicious CosmWasm contract designed to exploit the ibc-hooks timeout path.
    3. They first ran a small proof transaction using a 7 USDC IBC transfer. According to Rarma’s on-chain reconstruction, the transfer timed out, the hook re-entered MsgTimeout, and the attacker turned that 7 USDC into 28 USDC of refunded balance.
    4. After confirming the path, they increased the fake refund amounts stepwise from 7,000 USDC to 28,000 USDC, 112,000 USDC, and eventually into much larger values, using the same timeout-callback recursion to mint or release counterfeit Terra-side bridged assets.
    5. They repeated the same pattern for USDT and axlWBTC, producing large fake balances from single timeout rights.
    6. With the stablecoin / BTC leg proven, they bought roughly 124,000 ASTRO with 6,000 USDC, giving themselves a real starter balance in the migration-era ASTRO asset that had been IBC-transferred from Neutron onto Terra.
    7. They then used the same timeout-callback recursion against ASTRO, ultimately manufacturing about 58M-60M ASTRO on Terra.
    8. Roughly 33M+ ASTRO was bridged back to Neutron, where about 13M ASTRO was swapped into axlUSDC and routed onward to Ethereum; a large residual ASTRO balance remaining on Terra was later frozen.
    9. The exploit path therefore cashed out in two ways at once: direct counterfeit USDC / USDT / BTC extraction and secondary monetization of counterfeit migration ASTRO through Astroport / Neutron liquidity and cross-chain routes.
  • What the attacker needed to be true:
    • Terra had to be running a vulnerable ibc-hooks / ibc-go combination despite the April 2024 advisory.
    • CosmWasm contract upload and execution had to remain available so the attacker could deploy the malicious callback contract.
    • The vulnerable timeout path had to keep packet commitments live long enough for the same MsgTimeout handling to be re-entered before refund state was consumed.
    • The counterfeit Terra-side bridged assets had to remain transferable, swappable, or bridgeable before operators halted the chain.
    • Migration or pool liquidity, especially for ASTRO, had to exist on Terra / Neutron / Axelar routes so the fake balances could be converted into stronger assets.
  • Technical anatomy worth remembering:
    • This was not a generic IBC bridge got hacked story. The decisive failure was a chain-local reentrancy bug inside the timeout callback path of ibc-hooks wrapping ICS-20.
    • The bug let the attacker spend the same refund right more than once before the protocol deleted the state that was supposed to make timeout handling single-use.
    • That makes Astroport / Terra a useful cousin to classic EVM reentrancy cases even though the mechanics look different. The callback came from CosmWasm + IBC middleware instead of ERC-777 or ERC-721 hooks, but the invariant break was the same: external control returned before accounting truth was finalized.
    • It is also a clean seam case between reentrancy and idempotency failure. The exploit only worked because the timeout path was re-enterable, but what got monetized was a replay of one logically one-shot refund.
    • Astroport matters here because Terra still held a large migration-era inventory of Neutron ASTRO on the old chain, so a chain-level counterfeit-voucher primitive could turn directly into protocol-specific token overissuance and market impact.
  • Representative transactions and contracts:
    • ibc-go advisory for the underlying bug: GHSA-j496-crgh-34mx / ASA-2024-007
    • Attacker Terra wallet: terra1wrve5z5vsmrgy6ldcveq93aldr6wk3qmxavs4j
    • Secondary Terra wallet used in exploit execution: terra1ra4hejz65ss8y52umg7pmerueedtfn9evnln0r
    • Attacker Neutron wallet: neutron16wynag7xgfy35sp8c5ls25c0je7dydmvq5pnd8
    • Attacker Ethereum cashout wallet: 0xBDe173c4C2249d3a98cD6ed844a4421728114F5A
    • Representative Terra exploit transaction cited by Rarma: 758F1280F4E8DB47C8C716F29EC418CCEFF52A782377F68AAC33FD92DA36169E
    • Representative Neutron swap / cashout transaction cited by Rarma: 188D612A00DA692A68F8A52C2EA533CEC3CFC5DA5F73E20D343727F568355C68
    • Representative Axelar bridge-out transaction cited by Rekt: 7E28A2BDD3A6DBED27269C23D0BDA2FBE4B2BE7F613E87CC23A237DA473F14E2
  • Response / mitigation:
    • Terra validators halted the chain for an emergency upgrade once the exploit path was recognized.
    • Public updates said validators with more than 67% of voting power upgraded and restarted the chain roughly 3.5 hours later with the vulnerable path patched again.
    • Astroport later reported that around 20M ASTRO still sitting on Terra in the attacker’s address was frozen, while ASTRO bridged to Neutron was removed from the exploiter wallet through TokenFactory force-transfer powers.
    • The incident became a pointed ecosystem lesson in regression management: the underlying issue had already been found, coordinated privately, and published publicly months earlier.

Pattern tags

  • one timed-out IBC refund right stayed live across a user-controlled callback boundary
  • packet-commitment deletion landed after CosmWasm hook execution
  • known critical ibc-go fix was reverted in a later chain upgrade
  • counterfeit Terra-side vouchers were accepted downstream as ordinary bridgeable and swappable balances
  • migration inventory on a legacy chain became live cash-out capacity for a chain-level counterfeit-mint bug

Why the exploit worked

  • Terra’s vulnerable ibc-hooks wrapper authenticated timeout execution with a packet commitment that still existed while the OnTimeout callback was running. That meant the chain had not yet consumed the one-shot refund right at the moment it handed control to attacker code.
  • The attacker did not need to fake IBC verification or forge a source-chain deposit. They only needed one real timed-out packet plus a malicious CosmWasm callback that could recurse into MsgTimeout before packet-commitment deletion landed.
  • Once Terra re-minted or released the same voucher repeatedly, downstream systems treated those balances as money-good because voucher authenticity was inferred from local bank / IBC balances, not from immediate escrow reconciliation or route-specific bounded-release checks.
  • Astroport’s migration-era ASTRO inventory turned a chain-level middleware bug into protocol-specific loss. Terra still held a large amount of Neutron-origin ASTRO precisely so users could migrate; that stock became the bridgeable, swappable inventory against which counterfeit balances could monetize.
  • The incident was also a governance / operations failure, not only a code failure: the bug class was already disclosed and patched upstream, so the decisive mistake was allowing a June upgrade to silently reopen a critical timeout-callback path on a chain that still permitted the callback prerequisites.

Detection and audit pivots

  • Trace timeout and acknowledgement flows exactly the way EVM auditors trace withdraw callbacks: where does packet-commitment deletion, refund-right consumption, or nonce invalidation actually occur relative to ibc-hooks, memo dispatch, or CosmWasm execution?
  • Reproduce MsgTimeout -> OnTimeout -> recursive MsgTimeout in tests with a malicious contract. For this bug family, one timed-out packet cannot create more than one refund or voucher remint is the invariant that matters.
  • Review every chain upgrade for security-regression risk, not just feature correctness. If a chain previously coordinated around a critical upstream patch, regression tests for that advisory belong in the permanent release gate.
  • Inventory the exact preconditions that make the bug live: vulnerable ibc-go, ibc-hooks wrapping ICS-20, and contract-upload / contract-execution rights that let an attacker supply the re-entrant callback target.
  • Reconcile outstanding voucher supply against escrowed or source-backed value during incident response. A local counterfeit-voucher bug stops being local the moment those balances can still bridge out or hit deep onchain liquidity.
  • Treat migration stockpiles and legacy-chain token inventories as blast-radius multipliers. If a deprecated chain still warehouses large balances for user convenience, those balances are part of the live security budget.

Failure-prevention checklist

  • Consume timeout rights before callback execution. Packet commitments, refund markers, or equivalent one-shot state should be deleted or irreversibly marked spent before ibc-hooks, memos, or CosmWasm callbacks receive control.
  • Make timeout handling prove second execution == zero additional value by construction. A recursive or repeated MsgTimeout for the same packet should revert or observe already-consumed state.
  • Keep one explicit timeout-refund security object per packet: channel, port, sequence, denom, sender, receiver, and refund entitlement should travel together instead of being reconstructed loosely across middleware layers.
  • If callback middleware cannot preserve one-shot semantics for a route, fail closed: disable ibc-hooks on ICS-20 timeout paths, narrow supported callback surfaces, or restrict CosmWasm uploads until the route is patched.
  • Add permanent regression tests for previously disclosed critical advisories. We patched this once is not enough if a later upgrade can silently roll the bug back in.
  • Continuously reconcile voucher liabilities against escrow balances or source-side backing and attach fast circuit breakers to deficits, sudden supply spikes, or impossible timeout-refund patterns.
  • Put bounded-release controls under bridged or migration-critical assets: per-denom caps, per-channel throttles, delayed large exits, and operator-run route pauses should assume a counterfeit local balance bug can eventually happen.
  • Treat migration inventories on legacy chains as custodial risk, not just UX convenience. Large stranded token stocks should be segregated, time-bounded, or rate-limited if they can be re-bridged or dumped through active liquidity.
  • Drill emergency response for chain-local counterfeit-asset events: who halts the chain, who freezes tokenfactory balances, which bridges or DEX routes pause first, and how quickly escrow deficits are measured.

Audit prompts

  • In the exact timeout path, where is the packet commitment deleted relative to OnTimeout hook execution, memo dispatch, and CosmWasm callbacks?
  • Can a malicious contract cause the same (channel, port, sequence) timeout to be processed more than once before the commitment is consumed?
  • Which chain configuration choices are part of the exploit surface: vulnerable ibc-go version, ibc-hooks wrapping ICS-20, contract-upload permissions, relayer behavior, or channel-specific middleware composition?
  • If counterfeit local vouchers appear, what concrete controls stop them from being swapped, migrated, or bridged into stronger external assets before operators react?
  • Are migration-era token inventories, tokenfactory balances, or legacy-chain reserves ring-fenced, rate-limited, or otherwise segregated from general market liquidity?
  • What invariant proves that one timed-out packet can never increase aggregate local voucher supply or escrow release by more than one legitimate refund amount?
  • Which release or upgrade tests explicitly replay prior critical advisories, and what would have caught Terra’s regression before mainnet restart?
  • During incident response, how quickly can operators measure voucher supply - escrow backing per denom and attach pauses to the routes that still permit cash-out?

Lessons that should already be standard

  • Timeout and refund paths are payout paths. If they move balances back to users, they deserve the same one-shot consumed-state discipline as withdrawals, claims, and bridge releases.
  • Cross-domain middleware callbacks are still callbacks. CosmWasm hooks, memos, and IBC timeout handlers should be threat-modeled like ERC-777 hooks or safeTransfer receivers, not like harmless bookkeeping.
  • Patch regression is a first-class security failure. Reopening a disclosed critical bug in a later upgrade is not bad luck or ops trivia; it is production reintroduction of a known exploit primitive.
  • Local voucher balances are not automatically money-good. If downstream bridges and pools accept them immediately, then counterfeit local minting is already an ecosystem-wide bounded-release problem, not merely a local accounting problem.
  • Legacy migration inventories are live treasury. Tokens parked on an old chain for convenience can become the exact liquidity surface that monetizes a chain-level bug.
  • Public contract-upload or callback-enablement policy is part of a chain’s effective security boundary. A vulnerability that requires malicious contract code is still catastrophic if the chain intentionally leaves that code path permissionless.
  • The clean invariant is brutally simple: one timed-out packet should authorize one refund and then become permanently dead state before any externalized logic can try to spend it again.

Why this incident matters

  • It preserves a non-EVM but mechanically clean reentrancy case where bridge middleware, not token standards, supplied the callback window.
  • It shows how a chain-level refund bug can become a protocol-specific drain when real liquidity or migration inventory sits downstream of the counterfeit asset.
  • It is also a strong reminder that patch regressions are not housekeeping trivia. Reintroducing a known critical callback bug can be just as costly as missing the bug the first time.

Internal linkages

  • Core recurring pattern: reentrancy.
  • Adjacent pattern: duplicate-claim-idempotency-failure — the same timeout refund right became consumable more than once because the one-shot state delete landed too late.
  • Positive-design companion: Exit-right consumed-state binding — one timed-out ICS-20 refund should have been consumed as a single-use exit right before the callback branch could do anything else.
  • Positive-design companion: Callback frame truth as a single security object — the timeout callback should have resumed one exact packet / refund frame once, not any recursively reachable MsgTimeout execution that still inherited live commitment state.
  • Adjacent bridge context: deposit-signal-locked-value-mismatch — once counterfeit vouchers existed, downstream bridge and liquidity paths treated them as if real source-side value had backed them.
  • Operational blast-radius companion: Bridge bounded-release design — even a local voucher-counterfeiting bug should have less room to escape if routes, assets, and migration inventories are throttled below ecosystem-drain scale.

Control surface