All incidents

0x7CAE Approved-Spender Drain

Share
Jul 08, 2023 02:40 UTCAttackLoss: 20,121.2 BONE, 37,656,666 WOOF +4 morePending manual check2 exploit txWindow: 12s
Estimated Impact
20,121.2 BONE, 37,656,666 WOOF +4 more
Label
Attack
Exploit Tx
2
Addresses
11
Attack Window
12s
Jul 08, 2023 02:40 UTC → Jul 08, 2023 02:40 UTC

Exploit Transactions

TX 1Ethereum
0x208f27f35fe302366252741c8cebe0d8861859ae9d7b17f8b2c62156bf5c8e4f
Jul 08, 2023 02:40 UTCExplorer
TX 2Ethereum
0xc42fc0e22a0f60cc299be80eb0c0ddce83c21c14a3dddd8430628011c3e20d6b
Jul 08, 2023 02:40 UTCExplorer

Victim Addresses

0x783e2f71d8967bdee8aa2ba0f3b9f402ac871365Ethereum
0x8f159f13f64db18b8a0742c86fa8b225cead6c5dEthereum
0x899b11881f977aeb5d9fac5105ce62c877f11763Ethereum
0x9eaaeab7255296e68ad1f12b969b9e30d1806c9dEthereum
0xc21a3b81efbba41dd319191b07a20eb1f5eebd61Ethereum
0x0a78fbeb89ee251c0d78e0eeb5e6bb7524a8939fEthereum
0x512e9701d314b365921bcb3b8265658a152c9ffdEthereum
0xf2cdd8b147802a07f862c9dc125190e0653795a2Ethereum
0x7e585b185fc67bc5f815b7abf459300418aa9f97Ethereum
0x26d61e57c44525d25aad4ef20bce3f7aa9d64c4cEthereum
0x7b05363f549c929c3da930f6728e3d74806e4103Ethereum

Loss Breakdown

20,121.2BONE
37,656,666WOOF
2,460,968,380.95SANI
2,584,625,207,128.320NE
10,966.05CELL
1,900USDC

Similar Incidents

Root Cause Analysis

0x7CAE Approved-Spender Drain

1. Incident Overview TL;DR

At Ethereum block 17646142, EOA 0xc0ccff0b981b419e6e47560c3659c5f0b00e4985 used freshly deployed helper contract 0xf466f9f431aea853040ef837626b1c59cc963ce2 to drain previously approved balances from multiple token holders through router 0x7caec5e4a3906d0919895d113f7ed9b3a0cbf826. The attacker contract finished the transaction holding BONE, WOOF, SANI, 0NE, CELL, and USDC that had been pulled directly from victim EOAs via transferFrom.

The root cause is a public approved-spender surface in 0x7cae. Its public selector 0x5ffe72b7 accepts a caller-chosen pool and later trusts that pool inside uniswapV3MintCallback, where the router spends from a payer decoded out of callback data. Because neither the entrypoint nor the callback authorizes the payer or constrains the recipient pool to a trusted set, any public caller can redirect balances from any address that already approved 0x7cae.

2. Key Background

0x7cae is a long-lived public router, not a single-use attacker deployment. Its creation metadata shows it was deployed in tx 0xcf8e9b4005763e5cbe8d5695a18b16005ab4e115894e203352bcad59e38705ae, and the collected Etherscan tx list shows successful public calls to selector 0x5ffe72b7 well before the incident. This matters because the exploit surface was already exposed to any searcher that inspected public state and bytecode.

The disassembly shows the router stores WETH in slot 0x98 and only checks that a caller-supplied pool reports WETH as token0() or token1(). An attacker can satisfy that requirement with a minimal custom pool contract. The helper contract 0xf466 was therefore only an execution wrapper: it is not the vulnerability, and the ACT property comes from the public router itself.

The victim side is a set of EOAs that had granted allowances to 0x7cae. The updated auditor artifact victim_code_check.txt confirms the listed affected holder addresses have no runtime code, so the incident is not explained by privileged victim contracts or hidden stakeholder logic.

3. Vulnerability Analysis & Root Cause Summary

This is an ATTACK-class ACT exposure centered on missing authorization in a public spender path. The critical invariant is straightforward: if router 0x7cae is approved as an ERC20 spender, any external execution path that spends from payer must verify that the caller is authorized to act for that payer and that the payout recipient is an approved route. The disassembly-derived breakpoint is the chain 0x5ffe72b7 at offset 0x067a -> uniswapV3MintCallback at offset 0x1117 -> pay() at offset 0x1bdd -> safeTransferFrom() at offset 0x20f6.

At 0x067a, the router stores a caller-supplied pool in slot 0x97 and immediately calls pool.mint(address(this), ...). At 0x1117, the callback only checks that msg.sender == sload(0x97), so any pool chosen by the attacker is trusted for that execution. From there, pay() routes to safeTransferFrom() whenever payer != address(this), and the payer comes directly from callback data. That lets a public caller cause transferFrom(victim, attacker-controlled recipient, amount) as long as the victim has already approved 0x7cae.

Observed router flow from the disassembly:

0x067a: public 0x5ffe72b7 stores caller-supplied pool in slot 0x97 and calls pool.mint(address(this), ...)
0x1117: uniswapV3MintCallback only checks msg.sender == slot 0x97
0x1bdd: pay() dispatch
0x20f6: safeTransferFrom(token, payer, pool, amount) when payer != address(this)

4. Detailed Root Cause Analysis

The seed transaction 0xc42fc0e22a0f60cc299be80eb0c0ddce83c21c14a3dddd8430628011c3e20d6b proves the exploit path on-chain. The call trace repeatedly shows the helper calling router selector 0x5ffe72b7, the router invoking the helper-controlled mint, and the callback forcing token spends from victim EOAs.

Observed seed trace excerpt:

0xF466...::0a5fede2(...)
  0x7CAE...::5ffe72b7(...)
    0xF466...::mint(0x7CAE..., 0, 0, 0, ...)
      0x7CAE...::uniswapV3MintCallback(0, 1900000000, 0x0000000000000000000000007b05363f549c929c3da930f6728e3d74806e4103)
        0xA0b86991...::transferFrom(0x7b05363f549c929C3dA930f6728e3D74806E4103, 0xF466F9f431aEa853040EF837626b1c59CC963ce2, 1900000000)

That exact USDC leg is mirrored in the balance diff. Victim 0x7b05363f549c929c3da930f6728e3d74806e4103 drops from 1900052720 to 52720, while helper 0xf466... rises from 0 to 1900000000. The same trace pattern repeats across the other affected tokens and holders. The router is therefore not merely interacting with attacker code; it is actively exercising its own spender allowance against third-party payers supplied in callback data.

The exploit conditions in the structured root cause hold against the collected evidence:

  • A victim must hold tokens and have a live allowance to 0x7cae.
  • The router must be unpaused.
  • The attacker must supply a pool that reports WETH on one side and can trigger the callback.
  • The target token must honor transferFrom for 0x7cae.

The fork PoC independently validates the mechanism without using the incident attacker identities. It recreates the historical USDC drain against the same approved victim with a fresh attacker EOA and a fresh locally deployed pool shim, which is consistent with the ACT framing.

5. Adversary Flow Analysis

The attack unfolded in two adversary-crafted transactions.

  1. Tx 0x208f27f35fe302366252741c8cebe0d8861859ae9d7b17f8b2c62156bf5c8e4f at block 17646141 deployed helper 0xf466f9f431aea853040ef837626b1c59cc963ce2.
  2. Tx 0xc42fc0e22a0f60cc299be80eb0c0ddce83c21c14a3dddd8430628011c3e20d6b at block 17646142 used that helper to batch 31 callback-driven drains across previously approved holders.

The helper's job was operational, not privileged. For each drain leg it selected a token, victim address, and amount, then called the public router path. The router accepted the helper as the pool, entered uniswapV3MintCallback, decoded the victim from callback data, and executed transferFrom into the helper. The helper therefore accumulated the drained balances solely because the public router transferred them there.

The adversary-related accounts are defensibly identified:

  • 0xc0ccff0b981b419e6e47560c3659c5f0b00e4985: deploying EOA and sender of the seed drain transaction.
  • 0xf466f9f431aea853040ef837626b1c59cc963ce2: freshly deployed helper contract and sole recipient of drained balances.

The affected holder set in the submitted analysis contains 11 EOAs: 0x783e2f71d8967bdee8aa2ba0f3b9f402ac871365, 0x8f159f13f64db18b8a0742c86fa8b225cead6c5d, 0x899b11881f977aeb5d9fac5105ce62c877f11763, 0x9eaaeab7255296e68ad1f12b969b9e30d1806c9d, 0xc21a3b81efbba41dd319191b07a20eb1f5eebd61, 0x0a78fbeb89ee251c0d78e0eeb5e6bb7524a8939f, 0x512e9701d314b365921bcb3b8265658a152c9ffd, 0xf2cdd8b147802a07f862c9dc125190e0653795a2, 0x7e585b185fc67bc5f815b7abf459300418aa9f97, 0x26d61e57c44525d25aad4ef20bce3f7aa9d64c4c, and 0x7b05363f549c929c3da930f6728e3d74806e4103.

6. Impact & Losses

The seed transaction drained balances from 11 holder EOAs across six ERC20 assets. The collected balance diff attributes the following net losses to the unauthorized router-spend path:

  • BONE: 20121200201296387000000
  • WOOF: 37656666000000000000000000
  • SANI: 2460968380949404909854552349
  • 0NE: 2584625207128315973299900000000
  • CELL: 10966052921140276000000
  • USDC: 1900000000

For 0NE, a portion of the transfer flow was routed to tax and burn destinations during transferFrom, but the holder-side negative delta still originates from the same unauthorized router callback path. The attacker-controlled helper contract ended the transaction holding the captured balances associated with the direct recipient legs, including the full 1900 USDC reproduced by the PoC.

7. References

  • Seed deployment tx: 0x208f27f35fe302366252741c8cebe0d8861859ae9d7b17f8b2c62156bf5c8e4f
  • Seed drain tx: 0xc42fc0e22a0f60cc299be80eb0c0ddce83c21c14a3dddd8430628011c3e20d6b
  • Router creation tx: 0xcf8e9b4005763e5cbe8d5695a18b16005ab4e115894e203352bcad59e38705ae
  • Early successful router activity: 0x549b4212b808cd6b6e6952cb5e0f80798a60d4e34cc0e41fcaf0e50a91263dbb and other successful 0x5ffe72b7 calls in the collected Etherscan tx list
  • Seed transaction metadata, trace, and balance diff under the collector artifacts for tx 0xc42fc0e...
  • Router and helper disassembly in the auditor artifacts: 7cae_disassembled.asm and f466_disassembled.asm
  • Victim EOA classification artifact: artifacts/auditor/iter_1/victim_code_check.txt
  • Reproduction test: poc/test/Exploit.sol