All incidents

CivTrade Fake-Pool Callback Drain

Share
Jul 08, 2023 15:16 UTCAttackLoss: 869,686.37 CIV, 50,000 USDC +3 morePending manual check10 exploit txWindow: 6m 24s
Estimated Impact
869,686.37 CIV, 50,000 USDC +3 more
Label
Attack
Exploit Tx
10
Addresses
2
Attack Window
6m 24s
Jul 08, 2023 15:16 UTC → Jul 08, 2023 15:22 UTC

Exploit Transactions

TX 1Ethereum
0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb
Jul 08, 2023 15:16 UTCExplorer
TX 2Ethereum
0x845f3c0fa982f35840ede0a445098202512cedabd75106da371029c0c5d0ce75
Jul 08, 2023 15:17 UTCExplorer
TX 3Ethereum
0x62e2ad77e4e9a0211a8019a40d7da1d696347afad3acfafdc3fefea46ef950e8
Jul 08, 2023 15:17 UTCExplorer
TX 4Ethereum
0x99aad76d0c0b182ebdde3f8e76b25dd24bbd24ed78410cc11bce5c4d81aaddb5
Jul 08, 2023 15:18 UTCExplorer
TX 5Ethereum
0xdd358de273b9c7723dd8cf6fdf68d24ef63a6f85bcfc6cfb519e0a149f8eff7d
Jul 08, 2023 15:18 UTCExplorer
TX 6Ethereum
0x2d3a2a93557a97632efbf6e59831bb281df0798e76c9a59398ae8b04c8c29115
Jul 08, 2023 15:19 UTCExplorer
TX 7Ethereum
0xa5ea83e00ed5ace4504fde74aff946cc13f52484c714773fb0532ac44fcf8f18
Jul 08, 2023 15:19 UTCExplorer
TX 8Ethereum
0x2ded5aa85183e835f447dd92f535fb2f15647d15becd643f1b36c9876aebd671
Jul 08, 2023 15:20 UTCExplorer
TX 9Ethereum
0xbefbbdb7e8332d5a6bb6575013c0433c4b7f9f1386af0c59155b42f6f6f94af1
Jul 08, 2023 15:21 UTCExplorer
TX 10Ethereum
0x8c85d0ccd80945a2e4b8ef58d85195f271cc4222e60b58482fdfe194ec6748a4
Jul 08, 2023 15:22 UTCExplorer

Victim Addresses

0xf169bd68ed72b2fdc3c9234833197171aa000580Ethereum
0x512e9701d314b365921bcb3b8265658a152c9ffdEthereum

Loss Breakdown

869,686.37CIV
50,000USDC
4,389.93ICE
3,000,000,000SHIB
65,029.58BMI

Similar Incidents

Root Cause Analysis

CivTrade Fake-Pool Callback Drain

1. Incident Overview TL;DR

CivTrade Positions NFT on Ethereum mainnet can be abused by any unprivileged attacker who deploys a fake Uniswap V3-style pool and passes that pool into the public CIVNFT entrypoint behind selector 0x7ca06d68. In the seed exploit transaction 0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb, attacker EOA 0xbf9df575670c739d9bf1424d4913e7244ed3ff66 used helper contract 0x1ae3929e1975043e5443868be91cac12d8cc25ec to make CIVNFT trust the helper as the mint callback sender, then forced uniswapV3MintCallback(uint256,uint256,bytes) to spend victim allowances and transfer 89789154803441368766010 raw CIV from victim 0x512e9701d314b365921bcb3b8265658a152c9ffd into the attacker-controlled contract.

The root cause is an access-control failure in callback authentication. CIVNFT binds its trusted callback sender to attacker-controlled calldata instead of a factory-verified pool, and its callback path honors attacker-controlled payer bytes. That combination lets a fake pool drain any approved holder of a supported token. The same helper contract was reused across nine more drain transactions and the overall campaign extracted CIV, USDC, ICE, SHIB, and BMI from unrelated approved users.

2. Key Background

The exploit relies on Uniswap V3 mint callback semantics. In a legitimate mint flow, only a real factory-created pool should be able to call uniswapV3MintCallback, and the payer whose tokens settle the mint should be tied to the actual user action that initiated the mint. CivTrade Positions NFT violates both expectations.

The relevant public protocol address is the CIVNFT proxy 0xf169bd68ed72b2fdc3c9234833197171aa000580, which delegates to implementation 0x78bd317a87d2eab65b666e9402182a949ab4eeb9. Before the campaign began, victims had already approved CIVNFT to spend supported ERC20 balances. The ACT pre-state for this case is Ethereum mainnet immediately before block 17649870, when transaction 0x5aa0742d19ec00705bdfe40e2e717ae6d5448151a552496083d72136549b7583 deployed the fake-pool helper.

The adversary helper is important because it mimics the surface CIVNFT expects from a Uniswap V3 pool. The attacker contract exposes token0(), token1(), slot0(), tickSpacing(), and mint(address,int24,int24,uint128,bytes), so CIVNFT treats it as a usable pool. Access control on that helper does not change the ACT classification, because any unprivileged attacker can deploy an equivalent helper contract and execute the same public call sequence.

3. Vulnerability Analysis & Root Cause Summary

This is an ATTACK-category protocol bug, not a pure MEV opportunity. CIVNFT selector 0x7ca06d68 accepts an arbitrary pool address from calldata, stores it in storage[0xfc], and then immediately calls pool methods on that unverified address. Later, uniswapV3MintCallback(uint256,uint256,bytes) authorizes callers only by checking whether msg.sender == storage[0xfc]. Because the attacker controls the pool address written into that slot, the attacker also controls the callback sender that passes the authorization check.

The second half of the bug is in payment settlement. During the callback path, CIVNFT decodes payer data from attacker-controlled callback bytes and uses that payer address when constructing the ERC20 transferFrom. The victim does not sign or send the exploit transaction; the only prerequisite is an existing token approval from the victim to CIVNFT. The intended invariant is straightforward: only a factory-authenticated pool should be able to trigger the mint callback, and callback payment should be bound to the actual user who initiated the mint. The concrete breakpoint is the storage[0xfc] write inside 0x7ca06d68, which later authorizes the malicious callback path.

4. Detailed Root Cause Analysis

The exploit path requires three conditions and nothing more:

  • The victim EOA has already approved CIVNFT to spend a supported ERC20.
  • The attacker can deploy a fake pool implementing the minimal interface CIVNFT expects.
  • CIVNFT is not paused when the public selector 0x7ca06d68 is called.

The vulnerable code path is visible in the victim implementation decompilation. First, the public entrypoint stores the caller-supplied pool into storage[0xfc]:

Victim implementation decompilation
0E4C    PUSH1 0xfc
0E4F    SLOAD
...
0E62    DUP4
0E63    AND
0E66    OR
0E69    SSTORE

That mutable slot is then reused by the callback gate:

Victim implementation decompilation
1C16    JUMPDEST
1C17    PUSH1 0xfc
1C19    SLOAD
...
1C23    CALLER
1C24    EQ
1C28    JUMPI
...
1C45    SLOAD

Those two snippets together show the invariant failure: attacker-controlled calldata decides which address is trusted as the later callback sender.

The seed trace shows the exploit unfolding exactly that way:

Seed exploit trace
0x1Ae3929E1975043E5443868be91CAc12d8cc25Ec::9910969e(...)
  TransparentUpgradeableProxy::fallback(0x7ca06d68...)
    0x78bd317a87d2Eab65b666e9402182A949Ab4EeB9::7ca06d68(...) [delegatecall]
      0x1Ae3929E1975043E5443868be91CAc12d8cc25Ec::tickSpacing() [staticcall]
      0x1Ae3929E1975043E5443868be91CAc12d8cc25Ec::mint(...)
        0x78bd317a87d2Eab65b666e9402182A949Ab4EeB9::uniswapV3MintCallback(
          89789154803441368766010,
          0,
          0x...512e9701d314b365921bcb3b8265658a152c9ffd...512e9701d314b365921bcb3b8265658a152c9ffd
        ) [delegatecall]
          CivilizationToken::transferFrom(
            0x512e9701D314b365921BcB3b8265658A152C9fFD,
            0x1Ae3929E1975043E5443868be91CAc12d8cc25Ec,
            89789154803441368766010
          )

The receipt confirms the same asset movement at the log layer. The seed transaction emitted a Transfer event on CIV moving 89789154803441368766010 raw units from 0x512e9701d314b365921bcb3b8265658a152c9ffd to 0x1ae3929e1975043e5443868be91cac12d8cc25ec, plus an Approval event showing the victim's allowance to CIVNFT being consumed. The attacker contract's direct CIV balance changed from 0 to 89789154803441368766010, while the attacker EOA separately paid 547299 * 43301918075 = 23699096460529425 wei in gas.

This seed transaction also establishes the case's non-monetary success predicate: an attacker-controlled contract that the victim never authorized directly received victim ERC20 funds solely by invoking CIVNFT selector 0x7ca06d68 and the follow-on uniswapV3MintCallback path. That is enough to prove the ACT even before later swaps monetize the stolen tokens.

5. Adversary Flow Analysis

The campaign followed a stable four-stage lifecycle.

First, the attacker EOA 0xbf9df575670c739d9bf1424d4913e7244ed3ff66 received ETH in transaction 0x7b6dd240eee9b89dc80a5bffb8012c48180994b86385b7249dfbf64d9a1c5ec8, which funded gas for the later deployment and drains.

Second, the attacker deployed helper contract 0x1ae3929e1975043e5443868be91cac12d8cc25ec in transaction 0x5aa0742d19ec00705bdfe40e2e717ae6d5448151a552496083d72136549b7583 at block 17649870. That contract bundled fake-pool methods and a withdrawal helper, so one deployment was enough for the whole campaign.

Third, the attacker executed ten drain transactions through the same helper:

  • 0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb
  • 0x845f3c0fa982f35840ede0a445098202512cedabd75106da371029c0c5d0ce75
  • 0x62e2ad77e4e9a0211a8019a40d7da1d696347afad3acfafdc3fefea46ef950e8
  • 0x99aad76d0c0b182ebdde3f8e76b25dd24bbd24ed78410cc11bce5c4d81aaddb5
  • 0xdd358de273b9c7723dd8cf6fdf68d24ef63a6f85bcfc6cfb519e0a149f8eff7d
  • 0x2d3a2a93557a97632efbf6e59831bb281df0798e76c9a59398ae8b04c8c29115
  • 0xa5ea83e00ed5ace4504fde74aff946cc13f52484c714773fb0532ac44fcf8f18
  • 0x2ded5aa85183e835f447dd92f535fb2f15647d15becd643f1b36c9876aebd671
  • 0xbefbbdb7e8332d5a6bb6575013c0433c4b7f9f1386af0c59155b42f6f6f94af1
  • 0x8c85d0ccd80945a2e4b8ef58d85195f271cc4222e60b58482fdfe194ec6748a4

Each of those calls reused the same idea: choose a victim/token pair, call the helper's public entrypoint, make CIVNFT trust the helper as the callback sender, and drain the victim's approved balance into the helper contract.

Fourth, the attacker monetized the stolen balances. Transactions 0xa4875b00fbdd5700a7820f7d216e1a1be7fc815da8e64e3d1136a0484a283a93 and 0xe660251cf080f4c7b0dc49e3b8dc7190e75d4a49bc0139f27751ffad448d3074 show the helper's withdrawal method being used and later swaps through 1inch.

6. Impact & Losses

The measurable campaign loss was:

TokenRaw amountDecimals
CIV86968636566027088958002118
USDC500000000006
ICE438993434486934002529118
SHIB300000000000000000000000000018
BMI6502957849096309349986018

The seed victim alone lost 89789154803441368766010 raw CIV, which is 89,789.15480344136876601 CIV, in a single attacker-crafted transaction. At campaign scale, the attacker drained approved ERC20 balances from multiple unrelated CivTrade users into the helper contract and later withdrew or swapped those assets. The loss pattern matches the report's non-monetary oracle exactly: unauthorized transfer of victim-approved ERC20 balances into an attacker-controlled contract through the fake-pool callback path.

7. References

  1. Seed transaction metadata: /workspace/session/artifacts/collector/seed/1/0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb/metadata.json
  2. Seed transaction trace: /workspace/session/artifacts/collector/seed/1/0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb/trace.cast.log
  3. Seed transaction receipt and logs: https://etherscan.io/tx/0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb
  4. Helper deployment transaction: https://etherscan.io/tx/0x5aa0742d19ec00705bdfe40e2e717ae6d5448151a552496083d72136549b7583
  5. Victim implementation decompilation: https://ethervm.io/decompile/0x78bd317a87d2eab65b666e9402182a949ab4eeb9
  6. Attacker EOA history: https://etherscan.io/address/0xbf9df575670c739d9bf1424d4913e7244ed3ff66
  7. CIVNFT token page: https://etherscan.io/token/0xf169bd68ed72b2fdc3c9234833197171aa000580