All incidents

Unauthenticated Pancake Callback Drain

Share
Sep 13, 2022 13:07 UTCAttackLoss: 25,912.95 USDT, 327.93 WBNB +4 morePending manual check1 exploit txWindow: Atomic
Estimated Impact
25,912.95 USDT, 327.93 WBNB +4 more
Label
Attack
Exploit Tx
1
Addresses
1
Attack Window
Atomic
Sep 13, 2022 13:07 UTC → Sep 13, 2022 13:07 UTC

Exploit Transactions

TX 1BSC
0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2
Sep 13, 2022 13:07 UTCExplorer

Victim Addresses

0x64dd59d6c7f09dc05b472ce5cb961b6e10106e1dBSC

Loss Breakdown

25,912.95USDT
327.93WBNB
22,307.55BUSD
5,160.32USDC
0.01499BTCB
0.097451ETH

Similar Incidents

Root Cause Analysis

Unauthenticated Pancake Callback Drain

1. Incident Overview TL;DR

In BNB Smart Chain block 21297410, EOA 0xee286554f8b315f0560a15b6f085ddad616d0601 sent transaction 0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2 to helper contract 0x5cb11ce550a2e6c24ebfc8df86c5757b596e69c1. That helper repeatedly invoked pancakeCall(address,uint256,uint256,bytes) on callback contract 0x64dd59d6c7f09dc05b472ce5cb961b6e10106e1d, causing the callback contract to transfer out all of its balances in USDT, WBNB, BUSD, USDC, BTCB, and ETH to the adversary EOA.

The root cause is an unauthenticated AMM callback path. The victim contract exposes pancakeCall and uniswapV2Call but does not verify that msg.sender is a real trusted pair before using msg.sender.token0() or msg.sender.token1() to choose which token to transfer. Any unprivileged actor can therefore deploy a fake pair-like contract, return arbitrary token addresses from those selectors, and drain the callback contract whenever it holds token balances.

2. Key Background

pancakeCall and uniswapV2Call are flash-swap callback entrypoints used by PancakeSwap and Uniswap V2 style pairs. They are only safe when the callee authenticates the caller as the exact pair expected for the swap. Without that check, any contract that implements the same selector surface can impersonate a pair.

The helper contract in the seed transaction only needed three relevant behaviors: expose token0(), expose token1(), and invoke the victim callback. The trace shows that this was enough to satisfy the victim contract's assumptions. Once the victim trusted the helper as if it were a pair, the helper could steer the victim into selecting attacker-chosen token contracts and executing outbound ERC20 transfers.

3. Vulnerability Analysis & Root Cause Summary

This is an ATTACK-category access control failure in a value-bearing helper contract. The vulnerable component is the callback contract at 0x64dd59d6c7f09dc05b472ce5cb961b6e10106e1d, specifically its pancakeCall(address,uint256,uint256,bytes) and uniswapV2Call(address,uint256,uint256,bytes) entrypoints.

The relevant invariant is simple: a DEX callback must not move assets unless msg.sender is the legitimate pair that initiated a real swap. Independent bytecode inspection confirms that the callback path beginning near bytecode offset 0x010a branches on the amounts, queries CALLER.token0() or CALLER.token1(), and then builds ERC20 transfer calls using the returned address, but it does not authenticate the caller first. The same bytecode contains owner-gated admin logic elsewhere, which shows that access control existed in the contract generally but was omitted on the callback path that actually moves funds.

4. Detailed Root Cause Analysis

The seed transaction metadata shows the top-level flow:

{
  "from": "0xee286554f8b315f0560a15b6f085ddad616d0601",
  "to": "0x5cb11ce550a2e6c24ebfc8df86c5757b596e69c1",
  "hash": "0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2",
  "blockNumber": "0x144f902"
}

Origin: seed transaction metadata.

The transaction trace then shows the helper calling the victim callback six times. For each call, the victim first staticcalls the helper for token0() and then transfers the full token balance to the attacker recipient encoded in calldata:

0x64dD59D6C7f09dc05B472ce5CB961b6E10106E1d::pancakeCall(..., 25912948173777791158265, 0, ...)
  0x5cB11ce550a2E6c24EBFC8DF86C5757b596e69c1::token0() [staticcall]
  0x55d398326f99059fF775485246999027B3197955::transfer(
    0xEE286554F8b315F0560A15b6f085dDad616D0601,
    25912948173777791158265
  )

Origin: seed transaction trace.

The same pattern repeats for WBNB, BUSD, USDC, BTCB, and ETH in the same transaction. The trace is therefore sufficient to show that the caller-controlled helper dictated the token address and that the victim then executed the transfers from its own balance.

Independent disassembly of the live victim bytecode matches that behavior. The callback path at 0x010a issues a STATICCALL to CALLER with selector 0x0dfe1681 (token0()) or 0xd21220a7 (token1()), then prepares selector 0xa9059cbb (transfer(address,uint256)) and performs a CALL to the returned token address:

00000132: CALLER
00000149: PUSH4 0x0dfe1681
...
0000016b: STATICCALL
...
0000022b: PUSH4 0xa9059cbb
...
0000029d: CALL

Origin: validator bytecode disassembly of 0x64dd59d6c7f09dc05b472ce5cb961b6e10106e1d.

No preceding pair or factory authentication appears on that execution path. That omission is the concrete breakpoint that turns a normal callback into a public drain primitive.

5. Adversary Flow Analysis

The adversary flow is fully contained in one transaction:

  1. The EOA 0xee286554f8b315f0560a15b6f085ddad616d0601 calls helper contract 0x5cb11ce550a2e6c24ebfc8df86c5757b596e69c1.
  2. The helper reads token balances on the victim contract and invokes pancakeCall once per token with amount0 equal to the full victim balance.
  3. Inside each callback, the victim treats the helper as if it were a trusted pair, asks it for token0() or token1(), and uses that address as the ERC20 to transfer.
  4. The victim transfers the full token balance to the attacker recipient encoded in calldata.

The trace excerpt below shows the repeated six-call draining sequence:

0x64dD59...::pancakeCall(... USDT amount ...)
0x64dD59...::pancakeCall(... WBNB amount ...)
0x64dD59...::pancakeCall(... BUSD amount ...)
0x64dD59...::pancakeCall(... USDC amount ...)
0x64dD59...::pancakeCall(... BTCB amount ...)
0x64dD59...::pancakeCall(... ETH amount ...)

Origin: seed transaction trace summary lines.

This sequence satisfies the ACT model because it requires no privileged key material, no private orderflow, and no dependency on attacker-owned on-chain artifacts beyond a fresh spoofing contract with public selectors.

6. Impact & Losses

The callback contract at 0x64dd59d6c7f09dc05b472ce5cb961b6e10106e1d was fully depleted of six ERC20 balances. The balance-diff artifact records the victim balances dropping to zero and the attacker EOA receiving the same raw amounts.

[
  {"token_symbol": "USDT", "amount": "25912948173777791158265", "decimal": 18},
  {"token_symbol": "WBNB", "amount": "327931283327916980816", "decimal": 18},
  {"token_symbol": "BUSD", "amount": "22307554466878046228172", "decimal": 18},
  {"token_symbol": "USDC", "amount": "5160324984279773039298", "decimal": 18},
  {"token_symbol": "BTCB", "amount": "14990282011559361", "decimal": 18},
  {"token_symbol": "ETH", "amount": "97450591261976269", "decimal": 18}
]

Origin: root cause loss summary and seed balance diff.

The attacker paid only 4904355000000000 wei of BNB in gas according to the native balance delta.

7. References

  1. Seed transaction 0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2.
  2. Victim callback contract 0x64dd59d6c7f09dc05b472ce5cb961b6e10106e1d.
  3. Helper contract 0x5cb11ce550a2e6c24ebfc8df86c5757b596e69c1.
  4. Seed transaction metadata in /workspace/session/artifacts/collector/seed/56/0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2/metadata.json.
  5. Seed trace in /workspace/session/artifacts/collector/seed/56/0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2/trace.cast.log.
  6. Seed balance diff in /workspace/session/artifacts/collector/seed/56/0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2/balance_diff.json.