CivTrade Fake-Pool Callback Drain
Exploit Transactions
0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb0x845f3c0fa982f35840ede0a445098202512cedabd75106da371029c0c5d0ce750x62e2ad77e4e9a0211a8019a40d7da1d696347afad3acfafdc3fefea46ef950e80x99aad76d0c0b182ebdde3f8e76b25dd24bbd24ed78410cc11bce5c4d81aaddb50xdd358de273b9c7723dd8cf6fdf68d24ef63a6f85bcfc6cfb519e0a149f8eff7d0x2d3a2a93557a97632efbf6e59831bb281df0798e76c9a59398ae8b04c8c291150xa5ea83e00ed5ace4504fde74aff946cc13f52484c714773fb0532ac44fcf8f180x2ded5aa85183e835f447dd92f535fb2f15647d15becd643f1b36c9876aebd6710xbefbbdb7e8332d5a6bb6575013c0433c4b7f9f1386af0c59155b42f6f6f94af10x8c85d0ccd80945a2e4b8ef58d85195f271cc4222e60b58482fdfe194ec6748a4Victim Addresses
0xf169bd68ed72b2fdc3c9234833197171aa000580Ethereum0x512e9701d314b365921bcb3b8265658a152c9ffdEthereumLoss Breakdown
Similar Incidents
WETH Drain via Unprotected 0xfa461e33 Callback on 0x03f9-62c0
36%NOON Pool Drain via Public transfer
35%V3Utils Arbitrary Call Drain
35%Dexible selfSwap allowance drain
34%0x7CAE Approved-Spender Drain
34%Unauthorized WETH drain via unprotected Uniswap V3 callback
33%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
0x7ca06d68is 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:
0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb0x845f3c0fa982f35840ede0a445098202512cedabd75106da371029c0c5d0ce750x62e2ad77e4e9a0211a8019a40d7da1d696347afad3acfafdc3fefea46ef950e80x99aad76d0c0b182ebdde3f8e76b25dd24bbd24ed78410cc11bce5c4d81aaddb50xdd358de273b9c7723dd8cf6fdf68d24ef63a6f85bcfc6cfb519e0a149f8eff7d0x2d3a2a93557a97632efbf6e59831bb281df0798e76c9a59398ae8b04c8c291150xa5ea83e00ed5ace4504fde74aff946cc13f52484c714773fb0532ac44fcf8f180x2ded5aa85183e835f447dd92f535fb2f15647d15becd643f1b36c9876aebd6710xbefbbdb7e8332d5a6bb6575013c0433c4b7f9f1386af0c59155b42f6f6f94af10x8c85d0ccd80945a2e4b8ef58d85195f271cc4222e60b58482fdfe194ec6748a4
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:
| Token | Raw amount | Decimals |
|---|---|---|
| CIV | 869686365660270889580021 | 18 |
| USDC | 50000000000 | 6 |
| ICE | 4389934344869340025291 | 18 |
| SHIB | 3000000000000000000000000000 | 18 |
| BMI | 65029578490963093499860 | 18 |
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
- Seed transaction metadata:
/workspace/session/artifacts/collector/seed/1/0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb/metadata.json - Seed transaction trace:
/workspace/session/artifacts/collector/seed/1/0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb/trace.cast.log - Seed transaction receipt and logs:
https://etherscan.io/tx/0x93a033917fcdbd5fe8ae24e9fe22f002949cba2f621a1c43a54f6519479caceb - Helper deployment transaction:
https://etherscan.io/tx/0x5aa0742d19ec00705bdfe40e2e717ae6d5448151a552496083d72136549b7583 - Victim implementation decompilation:
https://ethervm.io/decompile/0x78bd317a87d2eab65b666e9402182a949ab4eeb9 - Attacker EOA history:
https://etherscan.io/address/0xbf9df575670c739d9bf1424d4913e7244ed3ff66 - CIVNFT token page:
https://etherscan.io/token/0xf169bd68ed72b2fdc3c9234833197171aa000580